当前位置: 首页 > 科技观察

安卓屏幕适配麻烦吗?不!太容易了,..

时间:2023-03-22 17:36:42 科技观察

前言作为Android开发者,你还在为适配各种尺寸的屏幕而烦恼吗?你还在为一个新模型修改无数维度和布局吗?你还在为UI给的那些奇形怪状的设计图绞尽脑汁计算距离吗?如果你正在为这些事情所困扰,那么看完这篇文章,希望它能帮助你减少开发时间,放慢生活的速度。..不知道大家有没有看过前段时间头条技术团队发表的一篇关于Android屏幕适配的文章:一种成本非常低的Android屏幕适配方法。没看过的朋友可以在回来之前先看一看了解一下,这样才能更好的理解。无意中点开了这篇文章,看完后眼前一亮--------如果Android屏幕适配真的这么简单,那些日夜辛辛苦苦做适配的前辈就不会了可怕的死亡。......测试与思考不得不说,今日头条大师们的思路真的很独特,成本极低,而且非常好用。他们给出的最终方案是这样的:privatestaticfloatsRoncompatDennsity;privatestaticfloatsRoncompatScaledDensity;privatevoidsetCustomDensity(@NonNullActivityactivity,final@NonNullApplicationapplication){//applicationfinalDisplayMetricsappDisplayMetrics=application.getResources().getDisplayMetrics();if(sRoncompatDennsity==0){sRoncompatDennsity=appDisplayMetrics.密度;sRoncompatScaledDensity=appDisplayMetrics.scaledDensity;application.registerComponentCallbacks(newComponentCallbacks(){@OverridepublicvoidonConfigurationChanged(ConfigurationnewConfig){if(newConfig!=null&&newConfig.fontScale>0){sRoncompatScaledDensity=application.getResources().getDisplayMetrics().scaledDensity;}}@OverridepublicvoidonLowMemory(){}});}//计算出的宽度为360dp。同样,高度可以根据实际情况设置为640dp。finalinttargetDensityDpi=(int)(targetDensity*160);appDisplayMetrics.density=targetDensity;appDisplayMetrics.densityDpi=targetDensityDpi;appDisplayMetrics.scaledDensity=targetScaledDensity;//activityfinalDisplayMetricsactivityDisplayMetrics=activity.getResources().getDisplayMetrics();activityDisplayMetrics.density=targetDensity;activityDisplayMetrics.densityDpi=targetDensityDpi;activityDisplayMetrics.scaledDensitytarget;看完这篇文章,赶紧写了个demo测试了一下,发现了一个小问题。我们UI给出的设计图尺寸是1334*720。如果我以宽度为适配标准,按照设计图就是720px。width,屏幕的宽度应该是360dp,即:finalfloattargetDensity=appDisplayMetrics.widthPixels/360;这样,宽度适配的比例就没有问题了,但是我在想,如果需要用高度来适配(也就是内容刚好垂直填满整个屏幕),可不可以改成这样:finalfloattargetDensity=appDisplayMetrics.heightPixels/667;但是运行之后发现高度差异非常大,在不同分辨率和尺寸的手机上运行,??页面中各部分内容在垂直方向的比例并不相同,这确实达不到很好的适配效果。想了半天,发现一个问题:手头的测试机宽度是两个720和两个1080,高度是1280、1440、1780和一个全面屏2160。Android的开放性导致了Android设备尺寸碎片化严重,通过查看手机尺寸参数,会发现如果用这四款手机测试的话,宽度可以直接整除,但是高度不能(而且我有测试机的宽度也是可以整除的,如果有手机宽度不能整除怎么办?)。但是用今天头条给出的方法,除法后结果会四舍五入。难道是纵向计算的密度四舍五入影响了精度,导致结果不理想?修复问题后发现以上问题我开始修改,将计算结果取余后赋值给targetDensity。经过下午的反复测试和实验,我重新修改了targetDensity的计算方法:floattargetDensity=0;try{Doubledivision=Operation.division(appDisplayMetrics.heightPixels,667);//因为手机的长宽不是同样的,肯定会出现无穷无尽的情况,会出现精度损失,所以这里的结果是保留两位小数的操作DecimalFormatdf=newDecimalFormat("0.00");Strings=df.format(division);targetDensity=Float.parseFloat(s);}catch(NumberFormatExceptione){e.printStackTrace();}测试后发现,经过小数点后两位计算,合适的高度匹配结果很满意。但是还有一个问题。一般来说,我们是根据手机的宽度来做适配的,但偶尔也会有一个app中的一两个页面是根据高度(即内容垂直填满整个屏幕的页面)进行适配的。的。但是上面的方法只能保证一个方向,所以我只是让它自由切换适配的参考方向。最终方案继续修改后,得到了最终方案。修改后该类的所有内容如下:privatestaticfloatappDensity;privatestaticfloatappScaledDensity;privatestaticDisplayMetricsappDisplayMetrics;//该方法调用Density.setDensity(this);publicstaticvoidsetDensity(@NonNullApplicationapplicationinonCreateApplication){//获取DisplayMetricsappDisplayMetrics=application.getResources().getDisplayMetrics();if(appDensity==0){//初始化时赋值(Application中初始化时只调用一次)appDensity=appDisplayMetrics.density;appScaledDensity=appDisplayMetrics.scaledDensity;//添加字体变化监听application.registerComponentCallbacks(newComponentCallbacks(){@OverridepublicvoidonConfigurationChanged(ConfigurationnewConfig){//字体变化后,重新赋值appScaledDensityif(newConfig!=null&&newConfig.fontScale0){0)appScaledDensity=application.getResources().getDisplayMetrics().scaledDensity;}}@OverridepublicvoidonLowMemory(){}});}//调用修改密度值的方法(默认以宽度为基准)setAppOrientation(null,AppUtils.WIDTH);}//该方法用于在某个ActivityDensity中改变适配方向.setOrientation(mActivity,"宽度/高度");publicstaticvoidsetOrientation(Activityactivity,Stringorientation){setAppOrientation(activity,orientation);}/***targetDensity*targetScaledDensity*targetDensityDpi*这三个参数统一修改值**orientation:方向值,传入宽度或高度*/privatestaticvoidsetAppOrientation(@NullableActivityactivity,Stringorientation){floattargetDensity=0;try{Doubledivision;//根据传入的参数选择不同的适配方向if(orientation.equals("height")){//appDisplayMetrics.heightPixels/667division=Operation.division(appDisplayMetrics.heightPixels,667);}else{division=Operation.division(appDisplayMetrics.widthPixels,360);}//因为手机的长宽不一样,所以一定要在无穷无尽的情况下除法,有精度损失,所以这里的结果是保留两位小数的操作DecimalFormatdf=newDecimalFormat("0.00");Strings=df.format(division);targetDensity=Float.parseFloat(s);}catch(NumberFormatExceptione){e.printStackTrace();}floattargetScaledDensity=targetDensity*(appScaledDensity/appDensity);inttargetDensityDpi=(int)(160*targetDensity);/*******这里会修改值被分配给系统参数**(因为初始transform的时候,activity为null,所以设置application的值就可以了...*所以这里判断,如果有activity,则设置Activity的值)*/if(activity!=null){DisplayMetricsactivityDisplayMetrics=activity.getResources().getDisplayMetrics();activityDisplayMetrics.density=targetDensity;activityDisplayMetrics.scaledDensity=targetScaledDensity;activityDisplayMetrics.densityDpi=targetDensityDpi;}else{appDisplayMetrics.density=targetDensity;appDisplayMetrics.scaledDensity=targetScaledDensity;appDisplayMetrics.densityDpi=targetDensityDpi};}这是修改后的全部内容。不懂的可以看里面的评论。其中,我默认以width为基准(这是在Activity中设置的方法,存在于这个Activity下的fragment,dialog和PopupWindow都会受到这个效果的影响,也就是说设置之后一旦在Activity中,Activity下的其他子View就不需要再设置了)使用方法自己创建一个类,复制粘贴最终方案中的代码即可使用。使用方法:在Application的onCreate()方法中,如果只适配一个方向,设置这句话即可(我在utils中)默认设置为适应宽度,可以根据修改默认的适配方向你的需求,见下图)如果app中有页面需要垂直适配:/******因为是个人包,所以这个方法需要写在onCreate中的setContentView()方法前面(),切换方向的效果才会生效*/@OverridepublicvoidsetOrientation(){Density.setOrientation(this,AppUtils.HEIGHT);}/****如果在一个Activity中切换要适配方向,需要在destroy中设置方向为默认方向,*因为切换方向修改了Activity的值,但是application的值也会被覆盖(目前还不清楚原因。。。),*权衡利弊然后重新在onDestroy的生命周期中初始化方向(因为页面很少以高度为适配基准,这样可以尽量减少对程序功能的影响)*/@OverrideprotectedvoidonDestroy(){super.onDestroy();Density.setOrientation(this,AppUtils.WIDTH);}在某个Activity切换方向后,我修改了Activity中的值(activityDensity),但是返回点击其他页面后,发现其他页面的适配方向也被修改了,所以权衡利弊后,我采用了这种影响最小的方法:在需要修改适配方向的Activity的onDetroy生命周期中,手动将方向改为默认。..(鼓捣了半天实在想不出更好的办法,如果大家有其他好的办法可以给我留言)。***贴出纵向适配的效果图。页面中蓝色背景TextView的高度固定为150dp(就是自己写的一个很简单的页面,别嫌丑。。。):敲黑板!!!用这种方式写适配,只需要一个dimens文件和一个layout文件就够了。xml布局中,直接只能用dp(AndroidP刘海屏需要单独适配布局,看来全面屏手机上可以隐藏的虚拟按键也需要单独适配。。。)结论由于是自己写的demo,所以没有大规模测试。如果有条件大规模测试,有问题可以反馈给我,我们可以一起讨论如何修改,共同进步。这是我入行以来写的第一篇文章。如果有什么不对的地方请指正。以后我会继续努力写更多的文章。好东西需要分享。