Skip to content

sing1/UIAdapter

Repository files navigation

UIAdapter

转载标明出处

这是一篇关于Android屏幕适配的介绍,首先声明,不建议使用px作为单位,原因大家都知道。 ###图片镇楼


看到这两张图相信大家都不陌生了,没错,这就是苦逼的适配,在2012年,OpenSignalMaps发布了第一份Android碎片化报告,统计数据表明,
2012年,支持Android的设备共有3997种。
2013年,支持Android的设备共有11868种。
2014年,支持Android的设备共有18796种。
···
2017年...?


首先需要介绍一些概念:
原文地址

  • px(pixels)像素,屏幕上的点,不同设备显示效果相同,例如,HVGA代表320×480像素。
  • in(英寸)屏幕的物理尺寸, 每英寸等于2.54厘米。
  • pt(point)标准长度单位, 1pt=1/72英寸,用于印刷业,UI设计师会用,iOS字体单位,Android开发不涉及。
  • dpi(dots per inch) 打印分辨率,每英寸所能打印的点数,即打印精度; 每英寸点数,即每英寸包含像素个数。
  • ppi (pixels per inch)图像分辨率, 像素密度,在图像中, 每英寸所包含的像素数目。
  • density(屏幕密度), density和dpi的关系为 density = dpi/160。density这个概念方便理解不同dpi的倍数关系。
  • dp(也即dip,device independent pixels)设备独立像素,Android特有的单位,与密度无关的像素,基于屏幕密度的抽象单位,在320x480分辨率,同时每英寸160点(dpi = 160)的显示器上,1dp = 1px。
  • sp(scaled pixels)放大像素,与刻度无关的像素,字体单位,可以根据用户的字体大小首选项进行缩放。sp和dp一样,是android开发里特有的单位, 查看TextView的源码可知 Android 默认使用 sp 作为字号单位。 为什么要把sp和dp代替px?最简单的原因是他们不会因为ppi的变化而变化,在相同物理尺寸和不同ppi/dpi下,他们呈现的高度大小是相同。也就是说更接近物理呈现,而px则不行。

换算关系:
px = dp * (dpi / 160),原来这里的dpi是归一化后的dpi。
则dp = px / (ppi / 160)
ppi = √(长度像素数² + 宽度像素数²) / 屏幕对角线英寸数
dp*ppi/160 = px。比如1dp x 320ppi/160 = 2px。


gradle:

dependencies{... implementation 'com.github.sing1:UIAdapter:last_version' }

或者使用后面的代码自己生成,然后考到项目中,或者下载library中的代码拷贝,这个库适配的机型有:

240x320 120dpi240x400 120dpi240x432 120dpi320x480 160dpi480x640 240dpi
480x800 120dpi480x800 160dpi480x800 240dpi480x854 120dpi480x854 160dpi
480x854 240dpi640x960 320dpi600x1024 160dpi600x1024 240dpi600x1024 120dpi
720x1280 160dpi720x1280 240dpi720x1280 320dpi768x1280 320dpi768x1024 160dpi
768x1280 160dpi768x1280 240dpi800x1280 320dpi800x1280 160dpi800x1280 213dpi
1080x1920 420dpi1080x1920 480dpi1200x1920 320dpi1440x2560 560dpi1152x1536 240dpi
1152x1920 240dpi1200x1920 240dpi1536x2048 320dpi1536x2560 320dpi1600x2560 320dpi
1080x2400 440dpi1080x2252 440dpi1080x2296 440dpi

这个其中包括了横屏和竖屏,下面是生成文件的代码:

// 竖屏文件的输出路劲privatefinalstaticStringrootPathPort = "/Users/Sing/Desktop/res/values-port-{0}dpi-{1}x{2}/"; // 横屏文件的输出路劲privatefinalstaticStringrootPathLand = "/Users/Sing/Desktop/res/values-land-{0}dpi-{1}x{2}/"; // 水平方向(X)的值privatefinalstaticStringtemplateX = " <dimen name=\"dp_{0}_x\">{1}dp</dimen>\n"; // 垂直方向(Y)的值privatefinalstaticStringtemplateY = " <dimen name=\"dp_{0}_y\">{1}dp</dimen>\n"; // 默认模板信息,及参照尺寸,这里用的是1280x720 320dpiprivatestaticintdefaultDpi = 320; privatestaticintdefaultWidth = 720; privatestaticintdefaultHeight = 1280; privatestaticdoubledefaultScale = 2.0; // 将double类型的数据保留两位小数publicstaticStringtwoDemal(doublenum){DecimalFormatdFormat = newDecimalFormat("##0.00"); Stringresult = dFormat.format(num); returnresult} // 传入宽、高、DPI生成publicstaticvoidmakeString(intw, inth, intdpi){makeValue(w,h,dpi,0);// 竖屏XmakeValue(w,h,dpi,1);// 竖屏YmakeValue(w,h,dpi,2);// 横屏XmakeValue(w,h,dpi,3);// 横屏Y  } // type 0-3 分别为 竖屏X、竖屏Y、横屏X、横屏YprivatestaticvoidmakeValue(intw, inth, intdpi, inttype){doublescale = ((double) dpi / 160); // 缩放比例doubletotalWidthDp = defaultWidth / defaultScale; // 机型水平方向总共有多少个DPdoubletotalHeightDp = defaultHeight / defaultScale; // 机型垂直方向总共有多少个DPdoubleportWidthValue = (double) w / scale / totalWidthDp; // 竖屏情况下,参考模板水平方向(X) 每1dp对应的适配机型的值doubleportHeightValue = (double) h / scale / totalHeightDp;// 竖屏情况下,参考模板垂直方向(Y) 每1dp对应的适配机型的值doublelandWidthValue = ((double) h / w) * portWidthValue;// 横屏情况下,参考模板水平方向(X) 每1dp对应的适配机型的值doublelandHeightValue = ((double) w / h) * portHeightValue;// 横屏情况下,参考模板垂直方向(Y) 每1dp对应的适配机型的值StringrootPath = "";// 实际输出的路径FilelayFile = null; StringfileName = ""; StringBuffersb = newStringBuffer(); sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");// 文件头sb.append("<resources>\n");// 文件头if (type == 0){// 竖屏 Xfor (inti = 1; i <= 200; i++){sb.append(templateX.replace("{0}", i + "").replace("{1}", twoDemal(portWidthValue * i) + ""))} rootPath = rootPathPort; fileName = "dimens_x.xml"} elseif (type == 1){// 竖屏 Yfor (inti = 1; i <= 200; i++){sb.append(templateY.replace("{0}", i + "").replace("{1}", twoDemal(portHeightValue * i) + ""))} rootPath = rootPathPort; fileName = "dimens_y.xml"} elseif (type == 2){// 横屏 Xfor (inti = 1; i <= 200; i++){sb.append(templateX.replace("{0}", i + "").replace("{1}", twoDemal(landWidthValue * i) + ""))} fileName = "dimens_x.xml"; rootPath = rootPathLand} elseif (type == 3){// 横屏 Yfor (inti = 1; i <= 200; i++){sb.append(templateY.replace("{0}", i + "").replace("{1}", twoDemal(landHeightValue * i) + ""))} rootPath = rootPathLand; fileName = "dimens_y.xml"} sb.append("</resources>");// 文件尾Stringpath = rootPath.replace("{0}", dpi + "").replace("{1}", h + "").replace("{2}", w + ""); FilerootFile = newFile(path); if (!rootFile.exists()){rootFile.mkdirs()} layFile = newFile(path + fileName); try{PrintWriterpw = newPrintWriter(newFileOutputStream(layFile)); pw.print(sb.toString()); pw.close()} catch (FileNotFoundExceptione){e.printStackTrace()} } // 入口在这里,想生成什么机型就传入什么机型的信息,publicstaticvoidmain(String[] args){makeString(720, 1280, 320); makeString(1080, 1920, 480); // 这里可以输出你想要适配的机型 }

传入相应的机型后输出效果图如下:

使用生成的代码进行适配:

从图中可以看出,在1280x720和1920x1080的手机上,不管是横屏还是竖屏,控件的宽度和高度分别占据屏幕的1/2和1/4的的大小,由此说明,生成的文件计算的还是准确的,但是竖屏是个接近正方形的在横屏上却变成了长方形,是哪里出问题了?

<?xml version="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"> <Buttonandroid:id="@+id/button"android:layout_width="@dimen/dp_180_x"android:layout_height="@dimen/dp_160_y"android:background="#123123"android:textAllCaps="false"android:textColor="@android:color/white"android:textSize="16sp" /> </LinearLayout>

是因为控件的长度和高度是按手机的长宽计算的,所以适配的时候有时高度需要按照宽度来作为参考物,这个需要了解一下。


这个是第一版,如果有BUG的话欢迎指出!

About

android UI适配屏幕适配

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages