Android适配的痛点
Android
屏幕尺寸碎片化严重,需要使用dp
取代px
美工出图给出的值的单位为
px
,如何转化为dp
,我们基本靠目测
和定向适配
常见的适配方法
UI适配是Android开发的第一步,一般有3种办法去解决问题,官方方式、国内大神适配框架、预计算的三种方式:
官方:ConstraintLayout
预计算
我们重点讨论预计算
的方式,它的特点是:可以做到95%的比例适配,而且不需要消耗Runtime计算能力
。
预计算
原理解释:
-
只要研究过
ui适配
,肯定会知道dp
与px
的计算公式,因为这很重要,所以再看一次,加深印象:px = dp * (dpi / 160)
其中160是基准密度 -
android
定义了4种尺寸,与6种密度,有点多,我们先看看他的市场占有率(2017-12-11),如果我们只处理红色部分,涵盖了96.3%
的设备
- 分辨率表格
- mdpi(~160dpi) 相对密度:1
- hdpi(~240dpi) 相对密度:1.5
- xhdpi(~320dpi) 相对密度: 2
- xxhdpi(~480dpi) 相对密度: 3
- xxxhdpi(~640dpi) 相对密度:4
我们为常见分辨率做个表格,按照基准倍率进行测算,得到表格,内的值是该分辨率下宽度的dp长度
于是我们通过穷举(只要是穷举,肯定是一定是不完全统计),获取到宽度值,从小到大包括: 240dp,320dp,360dp,384dp,400dp,480dp,512dp,533dp
-
资源限定符
的概念, 主要关注sw<N>dp
这个限定符。
例如:对sw600dp的描述: 如果布局要求屏幕区域的最小尺寸
始终至少为 600 dp
,则可使用此限定符
创建布局资源 —res/layout-sw600dp/
。仅当可用屏幕的最小尺寸至少为600dp时(600dp wide and bigger
)
参考链接
- 最后,通常PD提供的效果图的尺寸是固定的,我们只需要完成效果图尺寸与屏幕尺寸的按照
长宽等比例映射
,就完成了。
得到公式:
按照等比例缩放:
屏幕宽度(dp)/控件长度(dp) = 效果图宽度(px)/ 控件长度(px)
,
即:
控件长度(dp)=屏幕宽度(dp) * 控件长度(px)/效果图宽度(px)
如何使用:
我们将屏幕划分为多个区间,用每个区间的最小值代表宽度,区间越密集,适配效果越好
。
比如我们把区间分为 sw240dp,sw320dp,sw360dp,sw384dp,sw400dp,sw480dp,sw512dp,sw533dp,我们可以通过批量计算,生成dimen文件,dimen文件的key代表像素值,value是对应的dp值。
<dimen name="px21">8dp</dimen>
<dimen name="px22">9dp</dimen>
优势
我们在使用过程中,可以完全按照PD提供的尺寸进行开发了,这种方法的优势在于: 连margin、padding等问题也都是等比例缩放的。
缺点:
问题的缺点在于,这个方法并不是完全的精准,如果我们切分足够细致,就可以解决此问题。
工具代码:
import org.apache.commons.io.FileUtils;
import java.io.File;
public class DimensGenerator {
private static String FOLDER = "/Users/doulala/Desktop/res"; //生成资源文件位置
private static int MAX_PX = 750; // 生成px值数量
private static int SAMPLE_RESOLUTION_WIDTH = 750;//效果图宽度
private static int[] widthes_dp = {240, 320, 360, 384, 400, 480, 512, 533}; //需要适配的屏幕的dp宽度
private static String template = "<dimen name=\"px{px}\">{dp}dp</dimen>";//配置模板
public static void main(String[] args) throws Exception {
File folder = new File(FOLDER);
if (!folder.exists()) {
folder.mkdirs();
}
FileUtils.cleanDirectory(folder);
for (int dp_width : widthes_dp) { //开始生成每个宽度的资源
File valueDictionary = new File(folder, "values-sw" + dp_width + "dp");
valueDictionary.mkdirs();
File dimenFile = new File(valueDictionary, "dimens.xml");
dimenFile.createNewFile();
StringBuilder stringBuilder = new StringBuilder();
String comment = "<!-- dimens for " + dp_width + "dp width device and " + SAMPLE_RESOLUTION_WIDTH + "px width sample -->"; //注释
stringBuilder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
.append("\n")
.append(comment)
.append("\n")
.append("<resources>")
.append("\n");
for (int px = 1; px <= MAX_PX; px++) { //开始处理每个配置项
int dp = dp2px(px, dp_width, SAMPLE_RESOLUTION_WIDTH); //计算dp
stringBuilder.append(" ");
stringBuilder.append(template.replace("{px}", String.valueOf(px)).replace("{dp}", String.valueOf(dp)));
stringBuilder.append("\n");
}
stringBuilder.append("</resources>");
FileUtils.write(dimenFile, stringBuilder.toString(), "utf-8");
}
System.out.println("successed!!");
}
/**
* 控件长度(dp)=屏幕宽度(dp) * 控件长度(px)/效果图宽度(px)
*
* @param px_target 测算的px值
* @param dp_width 设备的宽度,单位dp
* @param px_width 效果图宽度,单位px
* @return
*/
private static int dp2px(int px_target, int dp_width, int px_width) {
int dp = (int) Math.ceil(Double.valueOf(dp_width * px_target) / px_width);
return dp;
}
}