如何实现Android屏幕适配

文章目录

      • Android屏幕适配
        • 1.基础知识
        • 2.smallestWidth(最小宽度)限定符适配方案
          • 2.1.原理
          • 2.2.怎么确定设备匹配的`values-swdp` 文件夹
          • 2.3.怎么确定dimens.xml文件内的值
        • 3.今日头条适配方案
            • 3.1.原理
            • 3.2.实现

Android屏幕适配

1.基础知识

分辨率:手机的显示屏上能容纳的像素点。比如1080*1920,就是指横向能容纳1080个像素点,纵向能容纳1920个像素点。

dp:也叫dip(device independent pixels(设备独立像素))是一种基于屏幕密度抽象长度单位,对应于160dpi下像素的物理尺寸,对应于160dpi下像素的物理尺寸。

px:像素,屏幕上显示数据的最基本的点。

dpi:是Dots Per Inch的缩写,翻译过来就是每英寸(Inch)有多少点。

Android中的dp和px相互转换的计算公式:

  • px = dp * (dpi / 160)
  • density = dpi / 160
  • px = density * dp

其中dpi是根据屏幕真实的分辨率和尺寸来计算的,公式如下:
如何实现Android屏幕适配_第1张图片
如下图中显示的dpi是420:
如何实现Android屏幕适配_第2张图片
但是我们通过公式运算,那么计算结果得到的dpi是440,这明显和上面不一样,是不是一脸懵逼。
如何实现Android屏幕适配_第3张图片
其实我们算出来的值,不一定就是设备真实dpi,在android开发中所说的dpi的值并不是物理定义的,而是系统文件写进去的,所以这个值是可以被修改的。

那么怎么才能知道设备的真实dpi呢?

通过adb命令查看设备的具体dpi值: adb shell dumpsys window displays如何实现Android屏幕适配_第4张图片

其中 mDisplayId 为 显示屏编号,init 是初始分辨率和屏幕密度,如果app 的高度比 init 里的要小,表示屏幕底部有虚拟按键,高度为两者相减。

修改屏幕分辨率和dpi

adb shell
wm size 1080x1920
wm density 480
//wm size reset
//wm desity reset

好了进入正题,如何实现屏幕适配。

2.smallestWidth(最小宽度)限定符适配方案

2.1.原理

开发者为了做好适配会在工程中新建一系列 values-swdp 文件夹。

├── src/main
│ ├── res
│ ├── ├──values
│ ├── ├── ├──dimens.xml
│ ├── ├──values-sw320dp
│ ├── ├── ├──dimens.xml
│ ├── ├──values-sw360dp
│ ├── ├── ├──dimens.xml
│ ├── ├──values-sw400dp
│ ├── ├── ├──dimens.xml
│ ├── ├──values-sw411dp
│ ├── ├── ├──dimens.xml
│ ├── ├──values-sw480dp
│ ├── ├── ├──dimens.xml
│ ├── ├──...
│ ├── ├──values-sw600dp
│ ├── ├── ├──dimens.xml
│ ├── ├──values-sw640dp
│ ├── ├── ├──dimens.xml

values-swdp 文件夹含有dimens.xml文件,dimens.xml中就是对应的dp值。当项目运行到设备上时,系统会根据当前设备屏幕的最小宽度 (smallestWidth) 去匹配对应的 values-swdp 文件夹,如果没有值为N的则会去找与N相近的 values-swdp 文件夹,即X<=N。

2.2.怎么确定设备匹配的values-swdp 文件夹

上面说到系统会根据当前设备屏幕的最小宽度 (smallestWidth) 去匹配对应的 values-swdp 文件夹。
那么怎么确定设备屏幕的最小宽度 (smallestWidth) 呢?

不管屏幕此时是横屏还是竖屏,我们取设备屏幕高度和宽度中值最小的一方认为是最小宽度

如果想区分屏幕的方向来做适配该怎么办呢?那就只有再根据屏幕方向限定符生成一套资源文件,后缀加上 -land 或 -port 即可,像这样,values-sw400dp-land (最小宽度400dp横向),values-sw400dp-port (最小宽度400dp纵向)

假设:我们的设备的屏幕信息是 1080 * 1920、420 dpi。
设备宽是1080px,高是1920px,那么设备的最小宽度值就是1080px。
但是我们SmallestWidth(最小宽度)的单位是dp,所以我们要将1080px转换成dp。
根据dp和px相互转换的公式,我们得到的SmallestWidth(最小宽度)的值约是 411 dp (1080 / (420 / 160) = 411),设备将匹配到 values-sw411dp 文件夹下的 dimens.xml 文件。

对应的文件夹找到了,那么文件夹里面dimens.xml里的值怎么计算呢?

2.3.怎么确定dimens.xml文件内的值

如上所述,设备将匹配到 values-sw411dp 文件夹下的 dimens.xml 文件,那么该文件dimens.xml下的值都是怎么算出来的呢?

假如现在设计给你的设计稿是1080*1920px,设稿的最小宽度是1080px。

首先我们要知道sw适配是怎么使用的,我们实现UI时只要按照设计稿给出的宽高,在xml设置同样数值的sw_dp就行。

如:设计稿中又个控件宽高是1080*100px(宽度是整个屏幕的宽度),那么我们使用sw时就是

    <View
        android:layout_width="@dimen/sw_1080dp"
        android:layout_height="@dimen/sw_100dp" />

这样写代表什么?代表就是sw_1080dp = 1080px,然而在设备上1080px是等于411dp的。那么sw_1dp = 411/1080 = 1px = 0.38dp。

所以dimens.xml内的值就是values-swdp中(N)的值除以你的设计搞的最小尺寸M,即 N(dp) / M(px) = 1/density (N = 1080/density, M=1080)。

在5英寸、1080*2340px、480dpi下如图(1080/(480/160)=360, values-sw360dp):
如何实现Android屏幕适配_第5张图片如何实现Android屏幕适配_第6张图片
在5英寸、1080*2340px、400dpi下如图(values-sw432dp):
如何实现Android屏幕适配_第7张图片如何实现Android屏幕适配_第8张图片

3.今日头条适配方案

3.1.原理

根据dp和px的转换公式 :px = dp * density ,在代码中动态修改density的值。

TextView#setTextSize(int unit, float size)

public void setTextSize(int unit, float size) {
if (!isAutoSizeEnabled()) {
setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
}
}

private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
Context c = getContext();
Resources r;

if (c == null) {
r = Resources.getSystem();
}else {
r = c.getResources();
}

setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
shouldRequestLayout);
}

@UnsupportedAppUsage
private void setRawTextSize(float size, boolean shouldRequestLayout) {
if (size != mTextPaint.getTextSize()) {
mTextPaint.setTextSize(size);

if (shouldRequestLayout && mLayout != null) {
// Do not auto-size right after setting the text size.
mNeedsAutoSizeText = false;
nullLayouts();
requestLayout();
invalidate();
}
}
}

TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics)

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;
}

DisplayMetrics类

/**
* The absolute width of the available display size in pixels.
*/
public int widthPixels;
/**
* The absolute height of the available display size in pixels.
*/
public int heightPixels;
/**
* 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.
*
* 

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; /** * The screen density expressed as dots-per-inch. May be either * {@link #DENSITY_LOW}, {@link #DENSITY_MEDIUM}, or {@link #DENSITY_HIGH}. */ public int densityDpi; /** * 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;

DisplayMetrics 中和适配相关的几个变量:

  • DisplayMetrics#density 就是上述的density
  • DisplayMetrics#densityDpi 就是上述的dpi
  • DisplayMetrics#scaledDensity 字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值
3.2.实现

假设设计图宽度是540dp,以宽维度来适配。
那么适配后的 density = 设备真实宽(单位px) / 540,接下来只需要把我们计算好的 density 在系统中修改下即可,代码实现如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setCustomDensity(this);
setContentView(R.layout.activity_main);
.
.
.
}

private void setCustomDensity(@NonNull Activity activity) {

final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
final float targetDensity = activityDisplayMetrics.widthPixels / 540;
final int targetDensityDpi = (int) (targetDensity * 160);
activityDisplayMetrics.density = activityDisplayMetrics.scaledDensity = targetDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}

设计图宽度是540dp:
如何实现Android屏幕适配_第9张图片
设计图宽度是360dp:
如何实现Android屏幕适配_第10张图片
参考文档:
今日头条适配原文链接

你可能感兴趣的:(Android学习笔记,屏幕适配,sw适配,头条适配)