窗外的山竹, 在电线杆上多嘴 你说这一句 很有末日的感觉. 山竹来了, 连饭都没得吃, 简直不要太恐怖.
说在前面
为什么要做屏幕适配? 我前几天在郭霖大大的公众号里面看到一张图片, 我觉得很有说明性的.
由于 Android 系统的开放性, 任何用户, 开发者, 硬件厂商, 运营商都可以对 Android 系统和硬件进行定制, 修改成他们想要的样子. 上图就代表了各大品牌的手机碎片化的现状. 随着 Android 设备的增多, 设备碎片化, 系统碎片化, 屏幕尺寸碎片化, 屏幕碎片化的程度也在不断加深.
当 Android 系统, 屏幕尺寸, 屏幕密度出现碎片化的时候, 就很容易出现同一元素在不同手机上显示不同的问题. 有的时候可能在 4.3 寸屏幕上面的样子是完美的, 但是当其安装到 5.7 屏幕上面的就会出现很大的问题.
为了保证不同分辨率, 屏幕大小的用户体验效果一致, 从而引发了适配的课题
基本概念想.
1, 像素:
含义: 通常所说的像素, 就是 CCD/CMOS 上光电感应元件的数量, 一个感光元件经过感光, 光电信号转换, A/D 转换等步骤以后, 在输出的照片上就形成一个点, 我们如果把影像放大数倍, 会发现这些连续色调其实是由许多色彩相近的小方点所组成, 这些小方点就是构成影像的最小单位 "像素"(Pixel). 简而言之, 像素就是手机屏幕的最小构成单元.
单位: px(pixel),1px = 1 像素点 一般情况下 UI 设计师的设计图会以 px 作为统一的计量单位.
2, 分辨率:
含义: 手机在横向, 纵向上的像素点数总和 一般描述成 宽 * 高 , 即横向像素点个数 * 纵向像素点个数 (如 1080 x 1920).
单位: px(pixel),1px = 1 像素点
3, 屏幕尺寸 (in):
含义: 手机对角线的物理尺寸
单位 英寸 (inch), 一英寸大约 2.54cm 常见的尺寸有 4.7 寸, 5 寸, 5.5 寸, 6 寸
4, 屏幕像素密度 (dpi):
含义: 每英寸的像素点数. 例如每英寸内有 160 个像素点, 则其像素密度为 160dpi.
单位: dpi(dots per inch)
计算公式: 像素密度 = 像素 / 尺寸 (dpi = px / in)
标准屏幕像素密度 (mdpi): 每英寸长度上还有 160 个像素点 (160dpi), 即称为标准屏幕像素密度 (mdpi).
屏幕尺寸, 分辨率, 像素密度三者关系:
一部手机的分辨率是宽 x 高, 屏幕大小是以寸为单位, 那么三者的关系是:
5, 密度无关像素 (dp):
含义: density-independent pixel, 叫 dp 或 dip, 与终端上的实际物理像素点无关
单位: dp, 可以保证在不同屏幕像素密度的设备上显示相同的效果, 是安卓特有的长度单位.
6, 独立比例像素 (sp):
含义: scale-independent pixel, 叫 sp 或 sip
单位: sp, 字体大小专用单位 Android 开发时用此单位设置文字大小, 可根据字体大小首选项进行缩放; 推荐使用 12sp,14sp,18sp,22sp 作为字体大小, 不推荐使用奇数和小数, 容易造成精度丢失, 12sp 以下字体太小.
7,sp 与 dp 的区别:
dp 只跟屏幕的像素密度有关;
sp 和 dp 很类似但唯一的区别是, Android 系统允许用户自定义文字尺寸大小 (小, 正常, 大, 超大等等), 当文字尺寸是 "正常" 时 1sp=1dp=0.00625 英寸, 而当文字尺寸是 "大" 或 "超大" 时, 1sp>1dp=0.00625 英寸. 类似我们在 windows 里调整字体尺寸以后的效果 -- 窗口大小不变, 只有文字大小改变.
追到 android 源码, 发现系统内部用 applyDimension() (路径: android.util.TypedValue.applyDimension()) 将所有单位都转换成 px 再处理:
- /**
- * Converts an unpacked complex data value holding a dimension to its final floating
- * point value. The two parameters <var>unit</var> and <var>value</var>
- * are as in {@link #TYPE_DIMENSION}.
- *
- * @param unit The unit to convert from.
- * @param value The value to apply the unit to.
- * @param metrics Current display metrics to use in the conversion --
- * supplies display density and scaling information.
- *
- * @return The complex floating point value multiplied by the appropriate
- * metrics depending on its unit.
- */
- public static float applyDimension(int unit, float value,
- DisplayMetrics metrics)
- {
- switch (unit) {
- case COMPLEX_UNIT_PX:
- return value;
- case COMPLEX_UNIT_DIP:
- return value * metrics.density;
- case COMPLEX_UNIT_SP:
- 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;
- }
可以发现 dp 和 sp 的区别在于 density 和 scaledDensity 两个值上;
- /**
- * The logical density of the display. This is a scaling factor for the
- * Density Independent Pixel unit, where one DIP is one pixel on an
- * approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen),
- * providing the baseline of the system's display. Thus on a 160dpi screen
- * this density value will be 1; on a 120 dpi screen it would be .75; etc.
- *
- * <p>This value does not exactly follow the real screen size (as given by
- * {@link #xdpi} and {@link #ydpi}, but rather is used to scale the size of
- * the overall UI in steps based on gross changes in the display dpi. For
- * example, a 240x320 screen will have a density of 1 even if its width is
- * 1.8", 1.3", etc. However, if the screen resolution is increased to
- * 320x480 but the screen size remained 1.5"x2" then the density would be
- * increased (probably to 1.5).
- *
- * @see #DENSITY_DEFAULT
- */
- public float density;
- /**
- * A scaling factor for fonts displayed on the display. This is the same
- * as {@link #density}, except that it may be adjusted in smaller
- * increments at runtime based on a user preference for the font size.
- */
- public float scaledDensity;
屏幕适配的方案:
图片适配
关于图片适配呢? 我两个点我要提及:
1, 资源位置:
前面的介绍我们可以知道: 在 AndroidStudio 的资源目录 res 下有五个层级图片文件夹, 分别用来存放不同分辨率的图片:
drawable-ldpi : 低分辨率 (用的少了, 一般不再用)
drawable-mdpi: 中分辨率
drawable-hdpi: 高分辨率
drawable-xdpi: 较高分辨率
drawable-xxdpi: 超级高分辨率
drawable-xxxhpi: 顶级分辨率
在对应的文件夹下放置不同分辨率的图片就可以很好的对图片进行适配.
随着屏幕越来越大, 推荐 xxdpi 的一套切图, 这样就可以向下和向上兼容, 节省资源.
2, 图片拉伸:
如果对于 Android 有一定的了解的开发者, 都可能会晓得 imageview 有一个 scaleType 用来适配图片的大小:
android:scaleType="center" 保持原图的大小, 显示在 ImageView 的中心. 当原图的 size 大于 ImageView 的 size 时, 多出来的部分被截掉.
android:scaleType="center_inside" 以原图正常显示为目的, 如果原图大小大于 ImageView 的 size, 就按照比例缩小原图的宽高, 居中显示在 ImageView 中. 如果原图 size 小于 ImageView 的 size, 则不做处理居中显示图片.
android:scaleType="center_crop" 以原图填满 ImageView 为目的, 如果原图 size 大于 ImageView 的 size, 则与 center_inside 一样, 按比例缩小, 居中显示在 ImageView 上. 如果原图 size 小于 ImageView 的 size, 则按比例拉升原图的宽和高, 填充 ImageView 居中显示.
android:scaleType="matrix" 不改变原图的大小, 从 ImageView 的左上角开始绘制, 超出部分做剪切处理.
androd:scaleType="fit_xy" 把图片按照指定的大小在 ImageView 中显示, 拉伸显示图片, 不保持原比例, 填满 ImageView.
android:scaleType="fit_start" 把原图按照比例放大缩小到 ImageView 的高度, 显示在 ImageView 的 start(前部 / 上部).
android:sacleType="fit_center" 把原图按照比例放大缩小到 ImageView 的高度, 显示在 ImageView 的 center(中部 / 居中显示).
android:scaleType="fit_end" 把原图按照比例放大缩小到 ImageView 的高度, 显示在 ImageVIew 的 end(后部 / 尾部 / 底部)
布局适配
关于布局适配呢? 我也有两点要进行提及:
1, 建议:
不要使用绝对布局, 使用相对布局和线性布局来代替绝对布局.
2, 资源位置:
在屏幕适配中, 我们也可以和图片一样: 根据手机大小不一样的手机建立不同的布局, 比如说创建两个文件夹:
- layout-800 * 480
- layout-1280 * 720
手机会根据分辨率去找设定的不同大小的 layout 的布局.
Contractlayout 适配:
我在这里呢? 就不讲这个了, 详情呢? 请查看我前面的文章 ConstraintLayout 用法详解. 顺便也关注一下
尺寸适配 (dimens 适配)
在屏幕适配中, 我们也可以和图片一样: 根据手机大小不一样的手机建立不同的 dimens, 比如说创建两个文件夹:
- values-400*320
- values-800*480
手机会根据分辨率去找设定的不同大小的 dimens 的参数.
权重适配:
当布局占满屏幕宽或高的时候, 子布局可以使用权重适配. 例如 LinearLayout 中的 weight 属性.
dp dp dp
android 的单位 dp 本身就有适配的功能, 如下图所示:
所以用好 dp, 本身就是一种适配, 下面附一下 dp 转 px 的方法 供日后查看:
- fun Int.dp2Px(context: Context): Int =
- TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- this.toFloat(),
- context.resources.displayMetrics
- ).toInt()
- fun Int.px2Dp(context: Context): Int =
- (this.toFloat() / context.resources.displayMetrics.density).toInt()
- fun Int.sp2Px(context: Context): Int =
- TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_SP,
- this.toFloat(),
- context.resources.displayMetrics
- ).toInt()
- fun Int.px2Sp(context: Context): Int =
- (this.toFloat() / context.resources.displayMetrics.scaledDensity).toInt()
- fun Float.dp2Px(context: Context): Int =
- TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- this,
- context.resources.displayMetrics
- ).toInt()
- fun Float.sp2Px(context: Context): Int =
- TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_SP,
- this,
- context.resources.displayMetrics
- ).toInt()
代码的适配:
- // 获取手机屏幕的宽和高
- val widthPixels = resources.displayMetrics.widthPixels
- val heightPixels = resources.displayMetrics.heightPixels
- // 给 button 设置宽和高
- val layoutParams = bt_main_button.getLayoutParams()
- layoutParams.width = widthPixels / 2
- layoutParams.height = heightPixels / 2
- bt_main_button.setLayoutParams(layoutParams)
由上面代码可知: 我们可以计算屏幕的大小 来确定控件的大小 这样就能达到适配的效果.
说在最后:
适配是一门大学问, 无论是刚入行的新手 还是入行多年的老手 对于适配其实或多或少都有点怵的. 算了, 我不知道哔哔什么了, 窗外的那个吊车被台风吹到了我的窗前, 我现在有点虚 时刻都在注意着它的 "走位", 生怕他一个 e 闪将我留在这里. 不写了不写了.
来源: http://www.jianshu.com/p/5b35679295af