现在主流的屏幕适配方式有两种:
- smallestWidth限定符适配方案
- 今日头条适配方案
参考博客:
https://juejin.im/post/5bce688e6fb9a05cf715d1c2
https://juejin.im/post/5ba197e46fb9a05d0b142c62
https://www.jianshu.com/p/1302ad5a4b04
https://juejin.im/post/5b6250bee51d451918537021
下面这两中类型的适配方案进行解析。
一、smallestWidth限定符适配方案
如果您已经学读过上面的博客,相信您对smallestWidth限定符适配已经有了了解,我也是根据这几篇博客学习的,所以这里仅摘取了我感觉重要的部分。
1. 目录结构
2. 原理解析
首先,开发者先在项目中根据主流屏幕的最小宽度(smallestWidth)生成一系类的values-sw<N>dp文件夹(含有dimens.xml文件),项目运行时,系统根据当前设备屏幕的最小宽度去匹配对应的values-sw<N>dp文件夹,使用对应的dimens.xml文件中的值。
如果没有找到对应的文件夹,则向下寻找(小于或等于)的文件夹。
这里需要注意的是,最小宽度不是区分方向的,即无论宽度还是高度,那一边小,就认为那一边是最小宽度。如果想区分屏幕的方向做适配,那就需要在根据屏幕方向限定符生成一套资源文件,后缀加上-land或者-port即可,像这样,values-sw400dp-land(最小宽度400dp横向),values-sw400dp-port(最小宽度400dp纵向)
3. 存在问题
仅适配宽度,高度不适配,造成结果不同设置显示的效果不一致。
如图:宽360,高640
因为高度可以通过滑动来显示,所以,如果内容特别多,需要滚动条显示余下内容,则可以忽略上述问题。
解决方案:smallesWidth限定符适配的效果是让不同分辨率和密度的设备能达到以设计图等比缩放的适配,如果设备与设计图相差太大时并不能达到很好的适配效果,需要单独出图。
二、今日头条适配方案
1. 原理解析
首先来了解几个公式:
density = dpi / 160;
px = dp * density => 当前设备屏幕总宽度(px) = 设计图总宽度(dp) * density
设计图总宽度已知,density根据设备不同值不同,并且每个设备上的density是固定不变的,所以当前要适配的屏幕宽度因density的值而改变,如果固定density的值,则屏幕即可实现适配。所以今日头条的核心原理是,更改density的值,保证不同设备上density值一致,则屏幕总宽度一致。
2. Androidautosize介绍
2.1 引入autosize库
mplementation 'me.jessyan:autosize:1.1.2'
2.2 在AndroidManifest.xml文件中配置设计图尺寸
<meta-data android:name="design_width_in_dp" android:value="360"/> <meta-data android:name="design_height_in_dp" android:value="480"/>
2.3 自定义Activity,实现CustomAdapt接口,配置适配信息。
1 public class MainActivity extends AppCompatActivity implements CustomAdapt { 2 3 @Override 4 protected void onCreate(@Nullable Bundle savedInstanceState) { 5 super.onCreate(savedInstanceState); 6 setContentView(R.layout.activity_main); 7 } 8 9 /** 10 * 是否按照宽度进行等比例适配 (为了保证在高宽比不同的屏幕上也能正常适配, 所以只能在宽度和高度之中选一个作为基准进行适配) 11 * 12 * @return {@code true} 为按照宽度适配, {@code false} 为按照高度适配 13 */ 14 @Override 15 public boolean isBaseOnWidth() { 16 return false; 17 } 18 19 /** 20 * 返回设计图上的设计尺寸, 单位 dp 21 * {@link #getSizeInDp} 须配合 {@link #isBaseOnWidth()} 使用, 规则如下: 22 * 如果 {@link #isBaseOnWidth()} 返回 {@code true}, {@link #getSizeInDp} 则应该返回设计图的总宽度 23 * 如果 {@link #isBaseOnWidth()} 返回 {@code false}, {@link #getSizeInDp} 则应该返回设计图的总高度 24 * 如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸, {@link #getSizeInDp} 则返回 {@code 0} 25 * 26 * @return 设计图上的设计尺寸, 单位 dp 27 */ 28 @Override 29 public float getSizeInDp() { 30 return 0; 31 } 32 }
2.4 Activity放弃适配,让Activity实现CancelAdapter接口即可,比如修改density影响到了老项目中的某些Activity页面的布局实现,这时候就可以让这个Activity实现CancelAdapt接口。
1 public class MainActivity3 extends AppCompatActivity implements CancelAdapt { 2 @Override 3 protected void onCreate(@Nullable Bundle savedInstanceState) { 4 super.onCreate(savedInstanceState); 5 setContentView(R.layout.activity_main); 6 } 7 }
2.5 Fragment的自定义方式和Activity是一样的,只不过在使用前需要在App初始化时开启Fragment支持。
public class AutoSizeApplication extends Application { @Override public void onCreate() { super.onCreate(); // 开启Fragment支持 AutoSizeConfig.getInstance().setCustomFragment(true); } }
2.6 适配三方库页面
在使用主单位时可以使用ExternalAdaptManager来实现不修改第三方库源码的情况下,适配三方库的所有页面(Activity、Fragment)
由于AndroidAutosizeSIze要求需要自定义适配参数或取消适配的页面必须实现CustomAdapt、CancelAdapt,这是问题就来了,三方库是通过远程依赖的,我们无法修改它的源码,这时我们怎么让三方库的页面也能实现自定义适配参数或取消适配呢,别急,这个需求AndroidAutoSize也已经为你考虑好了,当然不会让你将第三方库下载到本地然后修改源码。
- 通过ExternalAdaptManager.addExternalAdaptInfoOfActivity(Class,ExternalAdaptInfo);将需要自定义的类和自定义适配参数添加进方法即可替代实现CustomAdapt的方式。
public class AutoSizeApplication extends Application { @Override public void onCreate() { super.onCreate(); // 适配三方库 AutoSizeConfig.getInstance().getExternalAdaptManager() .addExternalAdaptInfoOfActivity(MainActivity2.class, new ExternalAdaptInfo(true,400)); } }
- 通过ExternalAdaptManager.addCancelAdaptOfAcivity(Class)将需要取消适配的类添加进入方法即可替代实现CancelAdapt的方式
public class AutoSizeApplication extends Application { @Override public void onCreate() { super.onCreate(); // 三方放弃适配 AutoSizeConfig.getInstance().getExternalAdaptManager().addCancelAdaptOfActivity(MainActivity2.class); } }
需要注意的是,ExternalAdaptManager的方法虽然可以添加任何类,但是只能支持Activity、Fragment,并且ExternalAdaptManager是支持链条式调用的,以便于支持多个页面。
当然,ExternalAdaptManager不仅可以对三方库的一面使用,也可以让自己项目中的Activity、Fragment不实现CustomAdapt、CancelAdapt即可达到自定义适配参数和取消是配的功能。
2.7 当App中出现多线程,并且需要适配所有的进程,就需要在App初始化时调用initCompatMultiProcess()
public class AutoSizeApplication extends Application { @Override public void onCreate() { super.onCreate(); //App中出现多线程 AutoSize.initCompatMultiProcess(this); } }
3. 测试结果
代码:
<meta-data android:name="design_width_in_dp" android:value="360"/> <meta-data android:name="design_height_in_dp" android:value="480"/>
public class MainActivity extends AppCompatActivity implements CustomAdapt { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean isBaseOnWidth() { return true; } @Override public float getSizeInDp() { return 320; } }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <View android:layout_width="320dp" android:layout_height="480dp" android:background="@color/colorPrimary"/> </LinearLayout>
测试结果:
4. 获取屏幕相关参数代码:
DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); int heightPixels = ScreenUtils.getScreenHeight(this); int widthPixels = ScreenUtils.getScreenWidth(this); float xdpi = dm.xdpi; float ydpi = dm.ydpi; int densityDpi = dm.densityDpi; float density = dm.density; float scaledDensity = dm.scaledDensity; //dp = px/density float heightDP = heightPixels / density; float widthDP = widthPixels / density; float smallestWidthDP; if (widthDP < heightDP) { smallestWidthDP = widthDP; } else { smallestWidthDP = heightDP; }