Android开发: 拒绝 Activity 重建!onConfigurationChanged 实现平板横竖屏无缝切换

多数Android平板老项目初始仅支持横屏展示,在业务需求升级、设备适配多元化的场景下,需要兼容竖屏显示效果。常规适配方式需重构布局文件,改造成本高、易引发兼容bug。本文记录一种零布局修改、纯代码动态适配的横竖屏解决方案,通过配置变更监听,动态修改网格列表列数、Item间距、文本展示字数及分割线状态,无需重建页面,即可实现平板横竖屏无缝切换适配,适配效率高、稳定性强,可快速复用至各类列表业务页面。

步骤如下

1、在AndroidManifest.xml中activity的属性设置

ini 复制代码
android:screenOrientation="unspecified"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"

属性释义

  • unspecified:跟随系统屏幕方向,自动适配横竖屏,不固定页面展示方向。
  • configChanges 多属性组合:拦截屏幕方向、尺寸、布局变化触发的Activity重建,仅回调onConfigurationChanged方法,实现无刷新适配。

2、activity/Fragment 中的配置

以列表页为例,横屏时可以每行显示3个item,切换竖屏只能显示2个且item之间的间距也会变化,item中文字显示的最大字符限制也不同,通过在onConfigurationChanged中重新设置列表无需新增或变动布局文件。

ini 复制代码
private boolean isLandscape; // 屏幕是否为横屏
private int spanCount = 2; // 列数
private GridSpacingItemDecoration decoration;
private int itemDecoration = AppUtil.getDimension(R.dimen.y30); //间距

@Override
    protected void initView(View view) {
        
        //方向配置
        isLandscape = BaseUtil.getOrientation();
        if(isLandscape){
            spanCount = 3;
            itemDecoration = AppUtil.getDimension(R.dimen.y90);
        }
        decoration = new GridSpacingItemDecoration(spanCount, itemDecoration, false);
        binding.rvList.addItemDecoration(decoration);
        GridLayoutManager layoutManager = new GridLayoutManager(getContext(), spanCount);
        binding.rvList.setLayoutManager(layoutManager);
        adapter.setLandscape(isLandscape); //设置方向
        adapter.setSpanCount(spanCount); //设置列数
        binding.rvList.setAdapter(adapter);
    }


  @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
       
        binding.rvList.removeItemDecoration(decoration); //先移除上次设置,不然会叠加影响

        isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
        if(isLandscape){
            spanCount = 3;
            itemDecoration = AppUtil.getDimension(R.dimen.y90);
        }else{
            spanCount = 2;
            itemDecoration = AppUtil.getDimension(R.dimen.y90);
        }
        decoration = new GridSpacingItemDecoration(spanCount, itemDecoration, false);
        GridLayoutManager layoutManager = new GridLayoutManager(getContext(), spanCount);               
        binding.rvList.setLayoutManager(layoutManager);
        binding.rvList.addItemDecoration(decoration);
        if(adapter!=null){ //刷新
            adapter.setLandscape(isLandscape);
            adapter.setSpanCount(spanCount);
            UIHandler.getHandler().postDelayed(() -> adapter.notifyDataSetChanged(),100);
        }
   
    }


public static int getDimension(int id) {
   return (int) BaseApplication.getInstance().getResources().getDimension(id);
}
 /**
   * 获取屏幕方向
 */
public static boolean getOrientation() {
    Configuration mConfiguration = BaseApplication.getInstance().getResources().getConfiguration(); //获取设置的配置信息
    //横屏
     return mConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE;
}

3、adapter中设置

ini 复制代码
private int spanCount = 2;
private boolean isLandscape = false; // 是否横屏

public void setSpanCount(int spanCount) {
   this.spanCount = spanCount;
}
public void setLandscape(boolean isLandscape) {
   this.isLandscape = isLandscape;
}


 @Override
    protected void convert(@NotNull DataBindBaseViewHolder baseViewHolder, XXBean bean) {
        ItemxxBinding binding = (ItemxxBinding) baseViewHolder.getDataBinding();
        int position = baseViewHolder.getLayoutPosition();
        int size = getData().size();
        int lastRowCount = 0;
        if(size > 0){
            lastRowCount = size % spanCount;
        }
        if (lastRowCount == 0) {
            lastRowCount = spanCount;
        }
        int firstItemOfLastRow = size - lastRowCount;

        // 如果是最后一排,隐藏底线view;否则显示底线view 
        if (position >= firstItemOfLastRow) {
            binding.view.setVisibility(View.GONE);
        } else {
            binding.view.setVisibility(View.VISIBLE);
        }

        String desc = bean.getDesc();
        String name = bean.getAppName();
        int numDesc = 11;
        int numName = 7;
        if(isLandscape){
            numDesc = 20;
            numName = 10;
        }
        if (desc != null && desc.length() > numDesc) {
            desc = desc.substring(0, numDesc) + "...";
        }
        if (name != null && name.length() > numName) {
            name = name.substring(0, numName) + "...";
        }
        binding.tvDesc.setText(desc);
        binding.tvName.setText(name);
        
    }

1. 为什么要加configChanges配置?

默认情况下,屏幕旋转会触发Activity销毁重建,会导致列表滚动位置重置、页面闪烁、临时数据丢失。配置对应参数后,系统不会重建页面,仅回调onConfigurationChanged,可以手动精准适配UI,体验更流畅。

2. 为什么必须移除旧的ItemDecoration?

每次旋转屏幕都会新增一个间距装饰器,若不主动移除旧装饰器,会出现间距叠加、Item间距越来越大的UI bug,是横竖屏列表适配的高频踩坑点。

3. 为什么需要延迟刷新适配器?

屏幕旋转瞬间,视图布局尚未完成绘制,立即调用notifyDataSetChanged可能刷新失效。100ms轻微延迟可保证布局加载完成后再刷新UI,适配更稳定。

方案优势:低侵入、高稳定、全覆盖、易复用

相关推荐
方也_arkling1 小时前
【Java-Day15】API篇-ArrayList集合
java·开发语言
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第89题】【Mysql篇】第19题:Hash 索引和 B+ 树索引的区别?它们在使用方面的区别?
java·数据库·mysql·面试·哈希算法
我是一颗柠檬1 小时前
【Java后端技术亮点】动态路由权限(按钮级权限),细粒度控制到按钮级别
java·开发语言·后端·状态模式
Fanfanaas1 小时前
C++ 继承
java·开发语言·jvm·c++·学习·算法
蚰蜒螟1 小时前
走进 Linux 内核:从 touch 命令到磁盘 inode 的完整旅程
java·linux·前端
zzqssliu1 小时前
taocarts 跨境独立站 SEO 优化实践(多语言 + 反向海淘场景)
java·javascript·php
在繁华处1 小时前
Java从零到熟练(十一):Spring框架入门
java·开发语言·spring
小锋java12341 小时前
【技术专题】LangChain4j 开发Java Agent智能体 - 整合SpringBoot4
java·人工智能
飞翔中文网2 小时前
Java学习笔记之抽象类
java·笔记·学习