如何实现Android屏幕适配

Android屏幕适配

1.基础知识

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

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

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

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

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

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

其中dpi是根据屏幕真实的分辨率和尺寸来计算的,公式如下:
在这里插入图片描述
如下图中显示的dpi是420:

但是我们通过公式运算,那么计算结果得到的dpi是440,这明显和上面不一样,是不是一脸懵逼。
运算值
其实我们算出来的值,不一定就是设备真实dpi,在android开发中所说的dpi的值并不是物理定义的,而是系统文件写进去的,所以这个值是可以被修改的。

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

通过adb命令查看设备的具体dpi值: adb shell dumpsys window displays获取dpi

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

修改屏幕分辨率和dpi

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

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

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

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

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

2.2.怎么确定设备匹配的values-sw<N>dp 文件夹

上面说到系统会根据当前设备屏幕的最小宽度 (smallestWidth) 去匹配对应的 values-sw<N>dp 文件夹。
那么怎么确定设备屏幕的最小宽度 (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_<n>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-sw<N>dp中(N)的值除以你的设计搞的最小尺寸M,即 N(dp) / M(px) = 1/density (N = 1080/density, M=1080)。

在5英寸、1080*2340px、480dpi下如图(1080/(480/160)=360, values-sw360dp):
在这里插入图片描述在这里插入图片描述
在5英寸、1080*2340px、400dpi下如图(values-sw432dp):
400dpi在这里插入图片描述

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.
*
* <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;
/**
* 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:
在这里插入图片描述
设计图宽度是360dp:
在这里插入图片描述
参考文档:
今日头条适配原文链接

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值