问题
刚刚接手项目的时候,发现很多UI布局都是直接写的多少dp,完全没有考虑到适配问题,所以发现app的安装到手机上时,有些手机的ui显示是完全变形了,所以不得不想办法解决屏幕的适配问题,因为android平台碎片化的原因,需要对许多不同的机型进行适配,查了一些资料,整理了一些解决方案
解决方案
方案1
针对不同的机型,我们创建不同的屏幕尺寸的的values文件夹,然后创建dimens.xml文件。那这样了就能解决针对不同的机型进行适配,但是市面上的机型太多,而且随之后面出现新的分辨率的机型时,我们又要写一套dimens,这样不仅工作量会增大,而且app的体积也会随之增大,并不是一个很好的方案
方案2
这种方案也就是很多人提到的swdp限定符的方式,即SmllestWidth限定符适配方案
什么是SmllestWidth?
移动设备是允许屏幕旋转的,当屏幕旋转时,屏幕的宽高会互换,而使用SmllestWidth方案是不区分屏幕方向的,
它只会把屏幕宽高最小的一方当做最小宽度,这个最小宽度是根据屏幕而定的,是固定不变的。也就是无论你如何旋转,
只需要宽高相比,把小的那一边当做最小宽度
在SmllestWidth方案中 需要在dimens.xml写入dp值(即将对应的px转换为dp),那么这个dp值是如何计算出来的呢?
首先我们需要直到屏幕的密度density ,可根据 density=dpi/160 获得 dpi是每英寸像素
然后我们就可以根据密度(density),像素(px),计算出对应的dp dp=px/density=px/(dpi/160)
举个例子 屏幕的像素 1080*1920 dpi:480
我们会发现最小宽度为1080,它的密度为:480/160=3,而最小的像素为1080px,
所以我们最终算出的dp为:1080/3=360dp,这个360dp就被认为是最小宽度,那么屏幕会适配values-sw360dp里面的尺寸,
若没有该尺寸,它将找小于等于他的离得最近的尺寸,以此来减少适配的误差问题
不同的values-swdp文件夹下的dimens如何生成的?
对于不同尺寸的dimens文件的生成,需要考虑到两个因素:最小宽度基准值是多少/需要适配哪些最小宽度尺寸
最小宽度基准值
以某个屏幕尺寸为基准,确定把设备的屏幕分为多少份,然后所有的屏幕都将分为一样的份数,最后根据比例确定每一份的尺寸是多
少,这样就会生成不同对应尺寸的dimens文件。就比如说若以360dp为基准,把屏幕分为360份,每份为1dp,那屏幕的最小宽度为
400dp,也是要将其分为360份,那么每一份的尺寸为:400/360=1.1dp。
适配哪些最小宽度
比如你想适配的最小宽度有320dp、360dp、400dp、480dp,那么方案将在res下生成values-sw320dp,values-sw360dp、
values-sw400dp、values-sw480dp这几个资源文件夹。当在不同的尺寸的手机上运行时,将找对应的尺寸资源进行适配,若没有找
到将会找到相近的尺寸资源进行适配,即使有一些误差,但也不会差距很大,但是每生成对应的一种尺寸,都会占用一定的app的体
积,所以要合理的分配想要适配的尺寸
优缺点
优点
* 适配率高,极低概率出现意外
* 不会有任何性能损耗
* 适配范围可自由控制,不会影响其他第三方库
* 学习成本低
缺点
* 维护成本比较高
* 侵入性高,每一个布局中都会引入dimen,所以后续切换其他方案时成本非常高
* 无法覆盖全部机型,若需要全部适配,将以增大app的体积为代价
使用代码生成对应问文件和尺寸资源
DimenTypes 使用DimenTypes确认要生成那些尺寸
public enum DimenTypes {
//适配Android 3.2以上 大部分手机的sw值集中在 300-460之间
DP_sw__300(300), // values-sw300
DP_sw__310(310),
DP_sw__320(320),
DP_sw__330(330),
DP_sw__340(340),
DP_sw__350(350),
DP_sw__360(360),
DP_sw__370(370),
DP_sw__380(380),
DP_sw__390(390),
DP_sw__410(410),
DP_sw__420(420),
DP_sw__430(430),
DP_sw__440(440),
DP_sw__450(450),
DP_sw__460(460),
DP_sw__470(470),
DP_sw__480(480),
DP_sw__490(490),
DP_sw__400(400);
// 想生成多少自己以此类推
/**
* 屏幕最小宽度
*/
private int swWidthDp;
DimenTypes(int swWidthDp) {
this.swWidthDp = swWidthDp;
}
public int getSwWidthDp() {
return swWidthDp;
}
public void setSwWidthDp(int swWidthDp) {
this.swWidthDp = swWidthDp;
}
}
使用MakeUtils生成values文件夹以及dimens文件
public class MakeUtils {
private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
private static final String XML_RESOURCE_START = "<resources>\r\n";
private static final String XML_RESOURCE_END = "</resources>\r\n";
private static final String XML_DIMEN_TEMPLETE = "<dimen name=\"qb_%1$spx_%2$d\">%3$.2fdp</dimen>\r\n";
private static final String XML_BASE_DPI = "<dimen name=\"base_dpi\">%ddp</dimen>\r\n";
private static final int MAX_SIZE = 720;
/**
* 生成的文件名
*/
private static final String XML_NAME = "dimens.xml";
public static float px2dip(float pxValue, int sw,int designWidth) {
float dpValue = (pxValue/(float)designWidth) * sw;
BigDecimal bigDecimal = new BigDecimal(dpValue);
float finDp = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).floatValue();
return finDp;
}
/**
* 生成所有的尺寸数据
*
* @param type
* @return
*/
private static String makeAllDimens(DimenTypes type, int designWidth) {
float dpValue;
String temp;
StringBuilder sb = new StringBuilder();
try {
sb.append(XML_HEADER);
sb.append(XML_RESOURCE_START);
//备份生成的相关信息
temp = String.format(XML_BASE_DPI, type.getSwWidthDp());
sb.append(temp);
for (int i = 0; i <= MAX_SIZE; i++) {
dpValue = px2dip((float) i,type.getSwWidthDp(),designWidth);
temp = String.format(XML_DIMEN_TEMPLETE,"", i, dpValue);
sb.append(temp);
}
sb.append(XML_RESOURCE_END);
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
/**
* 生成的目标文件夹
* 只需传宽进来就行
*
* @param type 枚举类型
* @param buildDir 生成的目标文件夹
*/
public static void makeAll(int designWidth, DimenTypes type, String buildDir) {
try {
//生成规则
final String folderName;
if (type.getSwWidthDp() > 0) {
//适配Android 3.2+
folderName = "values-sw" + type.getSwWidthDp() + "dp";
}else {
return;
}
//生成目标目录
File file = new File(buildDir + File.separator + folderName);
if (!file.exists()) {
file.mkdirs();
}
//生成values文件
FileOutputStream fos = new FileOutputStream(file.getAbsolutePath() + File.separator + XML_NAME);
fos.write(makeAllDimens(type,designWidth).getBytes());
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端调用
public class DimenGenerator {
/**
* 设计稿尺寸(将自己设计师的设计稿的宽度填入)
*/
private static final int DESIGN_WIDTH = 375;
/**
* 设计稿的高度 (将自己设计师的设计稿的高度填入)
*/
private static final int DESIGN_HEIGHT = 667;
public static void main(String[] args) {
int smallest = DESIGN_WIDTH>DESIGN_HEIGHT? DESIGN_HEIGHT:DESIGN_WIDTH; // 求得最小宽度
DimenTypes[] values = DimenTypes.values();
for (DimenTypes value : values) {
File file = new File("");
MakeUtils.makeAll(smallest, value, file.getAbsolutePath());
}
}
}
运行后生成的文件
方案3
该方案是根据今日头条的适配方案而来的,即今日头条适配方案,该方案已经开源,可直接依赖使用
方案的使用( )
1.添加依赖库
implementation 'me.jessyan:autosize:1.1.2'
2.在清单文件中注册设计图的宽高尺寸(dp)
<manifest>
<application>
<meta-data
android:name="design_width_in_dp"
android:value="360"/>
<meta-data
android:name="design_height_in_dp"
android:value="0"/>
</application>
</manifest>
3.根据对应的屏幕尺寸在对应的布局中布局控件
4.若某个Activity的设计尺寸与清单文件中的尺寸不一样时,可以实现CustomAdapt接口,指定特定的屏幕尺寸(dp)
public class CustomAdaptActivity : AppCompatActivity , CustomAdapt {
//是否以屏幕宽度为适配基准
@Override
public boolean isBaseOnWidth() {
return false;
}
//适配的尺寸是多少(dp)
@Override
public float getSizeInDp() {
return 667;
}
}
5.若某个Activity不想适配,可以实现CancelAdapt接口
public class CancelAdaptActivity : AppCompatActivity , CancelAdapt {
}
6.可以在 pt、in、mm 这三个冷门单位中,选择一个作为副单位,副单位是用于规避修改 DisplayMetrics#density 所造成的对于其他使用 dp 布局的系统控件或三方库控件的不良影响,使用副单位后可直接填写设计图上的像素尺寸,不需要再将像素转化为 dp
AutoSizeConfig.getInstance().getUnitsManager()
.setSupportDP(false)
.setSupportSP(false)
.setSupportSubunits(Subunits.MM);
代码混淆
-keep class me.jessyan.autosize.** { *; }
-keep interface me.jessyan.autosize.** { *; }
优缺点
优点
* 使用成本低,操作简单,无需在布局时添加额外的操作和代码
* 侵入性低,该方案和项目完全解耦
* 由于修改的density在整个项目是全局的,所以只要一次修改,项目中所有地方都会受益,包含第三方控件和系统控件
* 没有任何性能的损耗
缺点
* 因只修改一次density,项目中所有的地方都会自动适配,所以也会作用于第三方控件,但是若第三方控件的尺寸和当前项目的设计尺寸差距非常大时,那么适配会出现很严重的问题,若需要解决,则可以取消当前Activity的适配效果,采用其他适配方案
设备屏幕信息
原理解析
* px:即像素,1px代表屏幕上一个物理像素点
* dp(dip):设备无关像素,只要针对控件尺寸大小 dp=px/density
* sp:与缩放无关的抽象像素,类似于dp,但它主要针对文字尺寸大小
* density:屏幕密度,每一个设备的density都是固定的 density=dpi/160
适配方案最核心的思想是:根据公式算出density
dp=px/density
density=dpi/160
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX://单位为px 直接返回value
return value;
case COMPLEX_UNIT_DIP://单位为dip px=value*metrics.desity px=dp*density
return value * metrics.density;
case COMPLEX_UNIT_SP://单位为sp px=sp*metrics.scaledDensity
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
根据源码可知,无论使用任何单位,最终都会转化为px
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- baoquwan.com 版权所有 湘ICP备2024080961号-7
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务