响应系统设置的事件(Configuration类)

感谢你提到 响应系统设置的事件(Configuration 类)。在 Android 开发中,Configuration 类用于描述设备的配置信息(如屏幕方向、语言、字体大小、暗色模式等),当这些配置发生变化时,应用需要通过监听或处理机制来响应这些变化,以确保 UI 和逻辑适配当前设备状态。Android 提供了多种方式来监听和响应 Configuration 变化,例如通过 ActivityonConfigurationChanged 方法或 Configuration 对象的检查。本回答将详细讲解如何响应系统设置变化(基于 Configuration 类),结合你之前提到的组件(如 AlertDialogPopupWindowMenuViewPager2DrawerLayout)提供综合示例,并包括代码、优化建议及常见问题处理。如果你的需求涉及特定场景(如屏幕方向切换、语言变化、暗色模式适配或多点触碰结合),请提供更多细节,我可以进一步定制答案。


1. Configuration 类简介

定义
Configuration 类(android.content.res.Configuration)包含设备的当前配置信息,描述了设备的运行时状态,例如:

  • 屏幕方向orientationORIENTATION_PORTRAITORIENTATION_LANDSCAPE)。
  • 语言区域locale(如 en_USzh_CN)。
  • 字体大小fontScale(用户设置的字体缩放比例)。
  • 暗色模式uiModeUI_MODE_NIGHT_YESUI_MODE_NIGHT_NO)。
  • 屏幕密度densityDpi
  • 键盘类型keyboardnavigation

用途

  • 检测设备配置变化(如屏幕旋转、语言切换)。
  • 动态调整 UI(如切换布局、更新文本)。
  • 适配不同设备状态(如暗色模式、平板/手机)。

响应机制

  1. Activity 的 onConfigurationChanged:当配置变化(如屏幕旋转)时,系统调用此方法(需在 AndroidManifest.xml 中声明支持的配置变化)。
  2. 监听系统设置变化:通过 ContentObserver 或广播监听特定设置(如字体大小)。
  3. 手动检查 Configuration:通过 getResources().getConfiguration() 获取当前配置。

局限性

  • 默认情况下,配置变化(如屏幕旋转)会导致 Activity 重启,需正确处理以避免数据丢失。
  • 某些配置(如语言)变化可能需要手动刷新 UI。
  • 性能开销:频繁响应配置变化可能影响性能。

2. 响应 Configuration 变化的核心方法

  1. 声明支持的配置变化
    AndroidManifest.xml 中为 Activity 配置 android:configChanges,避免 Activity 重启:
   <activity
       android:name=".MainActivity"
       android:configChanges="orientation|screenSize|locale|uiMode|fontScale" />

常用配置项:

  • orientation:屏幕方向变化。
  • screenSize:屏幕尺寸变化(API 13+)。
  • locale:语言区域变化。
  • uiMode:夜间模式变化。
  • fontScale:字体大小变化。
  1. 重写 onConfigurationChanged
    在 Activity 中重写 onConfigurationChanged 方法,处理配置变化:
   @Override
   public void onConfigurationChanged(@NonNull Configuration newConfig) {
       super.onConfigurationChanged(newConfig);
       // 处理配置变化
   }
  1. 获取当前 Configuration
   Configuration config = getResources().getConfiguration();
   int orientation = config.orientation;
   Locale locale = config.locale;

3. 基本示例:响应屏幕方向和语言变化

以下是一个综合示例,展示如何监听 Configuration 变化(屏幕方向和语言),结合 DrawerLayoutViewPager2EditText(复用之前提到的 TextWatcher)动态更新 UI。

3.1 布局文件activity_main.xml

包含 DrawerLayoutToolbarEditTextViewPager2

3.2 菜单资源res/menu/nav_menu.xml

复用之前的侧滑菜单资源。

3.3 Fragment 页面PageFragment.java

复用之前的 Fragment,支持动态更新内容。


package com.example.myapp;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;

public class PageFragment extends Fragment {
private static final String ARG_PAGE = “page”;
private static final String ARG_CONFIG = “config”;
private TextView textView;

public static PageFragment newInstance(int page, String configInfo) {
    PageFragment fragment = new PageFragment();
    Bundle args = new Bundle();
    args.putInt(ARG_PAGE, page);
    args.putString(ARG_CONFIG, configInfo);
    fragment.setArguments(args);
    return fragment;
}

@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(android.R.layout.simple_list_item_1, container, false);
    textView = view.findViewById(android.R.id.text1);
    updateContent();
    return view;
}

public void updateConfig(String configInfo) {
    if (getArguments() != null) {
        getArguments().putString(ARG_CONFIG, configInfo);
        updateContent();
    }
}

private void updateContent() {
    int page = getArguments() != null ? getArguments().getInt(ARG_PAGE) : 0;
    String configInfo = getArguments() != null ? getArguments().getString(ARG_CONFIG, "") : "";
    textView.setText("页面 " + (page + 1) + "\n" + configInfo);
}

}

3.4 字符串资源res/values/strings.xml

添加多语言支持。

Open navigation drawer Close navigation drawer Home Profile Settings Enter text

中文资源res/values-zh/strings.xml):
打开导航抽屉 关闭导航抽屉 首页 个人中心 设置 请输入文本

3.5 AndroidManifest.xml

声明支持的配置变化。

3.6 Activity 代码MainActivity.java

响应屏幕方向和语言变化,更新 UI。


package com.example.myapp;

import android.content.res.Configuration;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.navigation.NavigationView;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
private ViewPager2 viewPager;
private EditText inputEditText;
private TextView configInfoText;
private TextWatcher textWatcher;
private ViewPagerAdapter viewPagerAdapter;

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

    // 初始化 Toolbar
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    // 初始化 DrawerLayout
    drawerLayout = findViewById(R.id.drawerLayout);
    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
            this, drawerLayout, toolbar,
            R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawerLayout.addDrawerListener(toggle);
    toggle.syncState();

    // 侧滑菜单处理
    NavigationView navigationView = findViewById(R.id.navigationView);
    navigationView.setNavigationItemSelectedListener(item -> {
        int itemId = item.getItemId();
        if (itemId == R.id.nav_home) {
            Toast.makeText(this, R.string.menu_home, Toast.LENGTH_SHORT).show();
            viewPager.setCurrentItem(0);
        } else if (itemId == R.id.nav_profile) {
            Toast.makeText(this, R.string.menu_profile, Toast.LENGTH_SHORT).show();
            viewPager.setCurrentItem(1);
        } else if (itemId == R.id.nav_settings) {
            String input = inputEditText.getText().toString();
            new AlertDialog.Builder(this)
                    .setTitle(R.string.menu_settings)
                    .setMessage("Current input: " + (input.isEmpty() ? "None" : input))
                    .setPositiveButton("OK", null)
                    .show();
        }
        drawerLayout.closeDrawers();
        return true;
    });

    // 初始化 ViewPager2
    viewPager = findViewById(R.id.viewPager);
    viewPagerAdapter = new ViewPagerAdapter(this);
    viewPager.setAdapter(viewPagerAdapter);

    // 初始化 EditText 和 TextWatcher
    inputEditText = findViewById(R.id.inputEditText);
    configInfoText = findViewById(R.id.configInfoText);
    textWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {}

        @Override
        public void afterTextChanged(Editable s) {
            String input = s.toString();
            if (input.equalsIgnoreCase("english")) {
                setLocale(new Locale("en"));
            } else if (input.equalsIgnoreCase("chinese")) {
                setLocale(new Locale("zh"));
            }
        }
    };
    inputEditText.addTextChangedListener(textWatcher);

    // 初始化配置信息
    updateConfigInfo(getResources().getConfiguration());
}

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    updateConfigInfo(newConfig);
    viewPagerAdapter.updateConfig(getConfigDescription(newConfig));
    Toast.makeText(this, "Configuration changed", Toast.LENGTH_SHORT).show();
}

private void updateConfigInfo(Configuration config) {
    String configInfo = "Orientation: " + (config.orientation == Configuration.ORIENTATION_PORTRAIT ? "Portrait" : "Landscape") +
            "\nLocale: " + config.locale.getDisplayName() +
            "\nFont Scale: " + config.fontScale +
            "\nNight Mode: " + (config.uiMode & Configuration.UI_MODE_NIGHT_MASK);
    configInfoText.setText(configInfo);
}

private String getConfigDescription(Configuration config) {
    return "Orientation: " + (config.orientation == Configuration.ORIENTATION_PORTRAIT ? "Portrait" : "Landscape") +
            ", Locale: " + config.locale.getDisplayName();
}

private void setLocale(Locale locale) {
    Locale.setDefault(locale);
    Configuration config = new Configuration();
    config.setLocale(locale);
    getResources().updateConfiguration(config, getResources().getDisplayMetrics());
    recreate(); // 刷新 Activity 以应用语言变化
}

@Override
public void onBackPressed() {
    if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
        drawerLayout.closeDrawer(GravityCompat.START);
    } else {
        super.onBackPressed();
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    inputEditText.removeTextChangedListener(textWatcher);
}

private static class ViewPagerAdapter extends FragmentStateAdapter {
    private final List<PageFragment> fragments = new ArrayList<>();
    private String configInfo = "";

    public ViewPagerAdapter(AppCompatActivity activity) {
        super(activity);
        for (int i = 0; i < 3; i++) {
            fragments.add(PageFragment.newInstance(i, configInfo));
        }
    }

    public void updateConfig(String configInfo) {
        this.configInfo = configInfo;
        for (PageFragment fragment : fragments) {
            fragment.updateConfig(configInfo);
        }
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return fragments.get(position);
    }

    @Override
    public int getItemCount() {
        return 3;
    }
}

}

3.7 运行效果

  • 界面:显示 ToolbarEditTextTextView(显示配置信息)和 ViewPager2(3 个页面,显示页面编号和配置信息)。
  • 屏幕方向
  • 旋转屏幕(横屏/竖屏),触发 onConfigurationChanged,更新 TextViewViewPager2 的配置信息(如“Orientation: Landscape”)。
  • 显示 Toast 提示“Configuration changed”。
  • 语言切换
  • EditText 输入“english”或“chinese”,动态切换语言(英文/中文)。
  • 界面刷新(recreate),菜单和提示文本自动适配新语言。
  • DrawerLayout
  • 点击汉堡图标或滑动打开侧滑菜单,点击“Settings”显示 AlertDialog(包含当前输入)。
  • 菜单项文本随语言变化更新。
  • ViewPager2:通过菜单切换页面,页面内容显示当前配置信息。

说明

  • android:configChanges:声明支持 orientationscreenSizelocale 等变化,避免 Activity 重启。
  • onConfigurationChanged:处理配置变化,更新 UI。
  • TextWatcher:监听 EditText,动态切换语言。
  • setLocale:更新应用语言并刷新 Activity。
  • ViewPagerAdapter:动态更新 Fragment 的配置信息。

4. 高级功能示例

以下展示更复杂的 Configuration 响应场景,结合你提到的组件。

4.1 响应暗色模式(uiMode)

动态调整 PopupWindow 的背景颜色以适配暗色模式。

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    PopupWindow popupWindow = new PopupWindow(new TextView(this), 200, 100, true);
    boolean isNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
    popupWindow.getContentView().setBackgroundResource(
            isNightMode ? android.R.color.black : android.R.color.white);
    ((TextView) popupWindow.getContentView()).setTextColor(
            isNightMode ? android.R.color.white : android.R.color.black);
    popupWindow.showAsDropDown(findViewById(R.id.inputEditText));
}

4.2 动态调整字体大小(fontScale)

根据字体大小调整 EditText 的文本大小。

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    float fontScale = newConfig.fontScale;
    inputEditText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16 * fontScale);
}

4.3 结合 ContentObserver 监听系统设置

监听系统字体大小变化(需权限)。


package com.example.myapp;

import android.content.ContentResolver;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;

public class FontScaleObserver extends ContentObserver {
private final MainActivity activity;

public FontScaleObserver(MainActivity activity) {
    super(new Handler(Looper.getMainLooper()));
    this.activity = activity;
}

@Override
public void onChange(boolean selfChange) {
    float fontScale = Settings.System.getFloat(
            activity.getContentResolver(), Settings.System.FONT_SCALE, 1.0f);
    activity.updateFontScale(fontScale);
}

}

在 Activity 中注册

// 在 onCreate 中
ContentResolver resolver = getContentResolver();
resolver.registerContentObserver(
        Settings.System.getUriFor(Settings.System.FONT_SCALE),
        true,
        new FontScaleObserver(this));

// 添加更新方法
public void updateFontScale(float fontScale) {
    inputEditText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16 * fontScale);
    Toast.makeText(this, "Font scale changed: " + fontScale, Toast.LENGTH_SHORT).show();
}

// 在 onDestroy 中
resolver.unregisterContentObserver(fontScaleObserver);

权限AndroidManifest.xml):

<uses-permission android:name="android.permission.WRITE_SETTINGS" />

5. 优化建议

  1. 避免 Activity 重启
  • 尽量声明所有需要处理的 configChanges,减少不必要重启:
    xml android:configChanges="orientation|screenSize|locale|uiMode|fontScale|keyboard|keyboardHidden"
  1. 性能优化
  • 避免在 onConfigurationChanged 中执行耗时操作:
    java new Thread(() -> heavyTask()).start();
  1. 数据保存
  • 保存状态以应对意外重启: @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putString("input", inputEditText.getText().toString()); } @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); inputEditText.setText(savedInstanceState.getString("input")); }
  1. 用户体验
  • 提供平滑过渡动画:
    java @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); configInfoText.animate().alpha(0).setDuration(200).withEndAction(() -> { updateConfigInfo(newConfig); configInfoText.animate().alpha(1).setDuration(200).start(); }).start(); }
  1. 多语言支持
  • 使用资源文件(res/valuesres/values-zh)确保字符串动态适配:
    xml <string name="input_hint">Enter text</string>

6. 常见问题及解决

  1. 配置变化未触发 onConfigurationChanged
  • 检查 AndroidManifest.xml 是否声明了 configChanges
    xml android:configChanges="orientation|screenSize"
  1. 语言切换不生效
  • 确保调用 recreate() 或手动刷新 UI:
    java recreate();
  1. 内存泄漏
  • 移除监听器:
    java @Override protected void onDestroy() { inputEditText.removeTextChangedListener(textWatcher); resolver.unregisterContentObserver(fontScaleObserver); super.onDestroy(); }
  1. 滑动冲突
  • 如果 ViewPager2DrawerLayout 与配置变化交互冲突,禁用触摸:
    java viewPager.setUserInputEnabled(false);
  1. 性能问题
  • 使用 Handler 延迟处理:
    java Handler handler = new Handler(Looper.getMainLooper()); handler.postDelayed(() -> updateConfigInfo(newConfig), 100);

7. 结合之前组件的 Configuration 响应

  • AlertDialog
  @Override
  public void onConfigurationChanged(@NonNull Configuration newConfig) {
      super.onConfigurationChanged(newConfig);
      new AlertDialog.Builder(this)
              .setTitle("Config Changed")
              .setMessage("Orientation: " + (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT ? "Portrait" : "Landscape"))
              .setPositiveButton("OK", null)
              .show();
  }
  • PopupWindow
  PopupWindow popupWindow = new PopupWindow(new TextView(this), 200, 100, true);
  @Override
  public void onConfigurationChanged(@NonNull Configuration newConfig) {
      super.onConfigurationChanged(newConfig);
      popupWindow.dismiss();
      popupWindow.showAsDropDown(inputEditText);
  }
  • Menu
  navigationView.setNavigationItemSelectedListener(item -> {
      String configInfo = getConfigDescription(getResources().getConfiguration());
      Toast.makeText(this, item.getTitle() + "\n" + configInfo, Toast.LENGTH_SHORT).show();
      return true;
  });
  • ViewPager2
  @Override
  public void onConfigurationChanged(@NonNull Configuration newConfig) {
      super.onConfigurationChanged(newConfig);
      viewPagerAdapter.updateConfig(getConfigDescription(newConfig));
  }
  • DrawerLayout
  @Override
  public void onConfigurationChanged(@NonNull Configuration newConfig) {
      super.onConfigurationChanged(newConfig);
      if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
          drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
      } else {
          drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
      }
  }

8. 结合多点触碰(TouchListener / onTouchEvent)

如果你希望结合之前的 多点触碰 需求,以下是一个示例,响应屏幕方向变化并调整多点触碰行为。

ImageView touchImage = findViewById(R.id.touchImage);
touchImage.setOnTouchListener(new View.OnTouchListener() {
    private float scale = 1.0f;
    private float lastDistance;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Configuration config = getResources().getConfiguration();
        float maxScale = config.orientation == Configuration.ORIENTATION_PORTRAIT ? 2.0f : 1.5f;

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                lastDistance = 0;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                if (event.getPointerCount() == 2) {
                    lastDistance = getDistance(event);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (event.getPointerCount() == 2) {
                    float newDistance = getDistance(event);
                    if (lastDistance > 0) {
                        float scaleFactor = newDistance / lastDistance;
                        scale = Math.max(0.5f, Math.min(scale * scaleFactor, maxScale));
                        v.setScaleX(scale);
                        v.setScaleY(scale);
                    }
                    lastDistance = newDistance;
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_UP:
                lastDistance = 0;
                break;
        }
        return true;
    }

    private float getDistance(MotionEvent event) {
        float dx = event.getX(0) - event.getX(1);
        float dy = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(dx * dx + dy * dy);
    }
});

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    Toast.makeText(this, "Orientation: " + (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT ? "Portrait" : "Landscape"), Toast.LENGTH_SHORT).show();
}

说明

  • 根据屏幕方向(orientation)动态调整缩放范围(maxScale)。
  • 横屏时限制缩放比例为 1.5,竖屏为 2.0。

9. 可能的其他意图

  • 特定配置:如果需要处理特定配置(如键盘、屏幕密度),请提供细节。
  • 组件集成:如果需要与 RecyclerViewBottomSheetDialog 等集成,请说明。
  • 数据可视化:如果需要将配置数据以图表形式展示(如字体大小变化趋势),我可以生成 Chart.js 图表,但需提供数据。
  • 跨平台需求:如果需要 iOS 或 Web 的配置响应方案(如 JavaScript 屏幕变化事件),请说明。
  • 问题调试:如果有具体问题(例如配置未触发、UI 未更新、性能),请描述。

下一步

请提供更多细节,例如:

  • 你需要响应的配置类型(屏幕方向、语言、暗色模式等)?
  • 是否需要特定组件的集成(RecyclerViewBottomSheetDialog 等)?
  • 是否需要适配特定 Android 版本或设备?
  • 是否有性能、内存或其他问题?

如果没有进一步信息,我可以提供更复杂的 Configuration 响应示例(例如结合 RecyclerView 的动态布局调整或多语言实时切换)或优化方案。

文章已创建 2349

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部