有关Android屏幕适配的部分总结

布局适配

首先要将布局适配的时候,我们要先讲一下Android中屏幕中的各种数据的关系

1.什么是屏幕尺寸、像素、分辨率和像素密度,密度无关像素?

  1. 屏幕尺寸: 我们通常用来形容机型几寸的屏幕,其实就是屏幕对角线的长度,1英寸=2.54厘米。
  2. 像素:用来标识像素点的个数单位。1px 代表一个显示设备中的一个像素点.
  3. 分辨率: 通常用来和清晰度有关,是横纵方向上存在的像素点数:如1080*1920。
  4. 像素密度(单位dpi):指的是每英寸上存在的存在的像素点数,比如: 在5寸、分辨率10801920的手 机上像素密度为 Math.sqrt(Math.pow(1080, 2) + Math.pow(1920, 2)) / 5 约为 440dip
    (**实际计算中 ,因为存在导航栏,虚拟按键等原因。分辨率会低于1080
    1920,如果需要具体数值可具体分析**)
  5. 密度(density):基准比例,密度计算方式 dpi/160;
  6. 密度无关像素 ,即我们常说的dp或者说dip是一样的,以160dpi为基准。在160dpi设备上1dp=1px,在240dpi设备上1dp=1.5px,以此类推,它的大小是由操作系统根据手机屏幕像素密度动态渲染出来的,1dp 对应多少 px 在不同的设备上,可能是不一致的。计算方式 px=dpi/160*dp

2.Android中帮我们实现的适配(dp)?

我们首先要明白一点事情是 无论在开发时使用什么尺寸单位,最终都需要转为像素(PX)
其次Google为Android的内置了几个默认的 Dpi。为了解决这个问题, Android 中 ,在特定的分辨率下自动调用,也可以手动在配置文件中修改。
在这里插入图片描述
在使用dp做单位时候,系统通过当前dpi在不同分辨率上最终会计算成不同的px值,
比如 120dp在上述几种默认配置中对应的大小分别(假定120dp为宽度):
90px 120px 180px 240px 360px
在可视化界面中的120dp对应的大小在上述几种相同尺寸不同分辨率中相对应的会增加
因此在不同的分辨率的手机上可以做初步的适配。
当前Android手机的厂商越来越多,相对应的分辨率/dpi都存在越来越多种类,所以单纯的采用dp形式已经不能满足我们的适配规则。

2.目前流行的几种布局适配规则

1. 百分比适配

最早由Google方便开发者进行适配开发,主要依赖

implementation ‘com.android.support:percent:27.0.2’

这里引用两个其他作者的文章进行表述
正常使用方式:https://www.jianshu.com/p/0c2a8db91bda
hongyang大神的进阶版:
http://blog.youkuaiyun.com/lmj623565791/article/details/46767825;出自:【张鸿洋的博客】

但是百分比适配缺点是:麻烦!
比如UI设计图给的都是px dp等,百分比需要自行计算;Android studio语法检查中xml布局宽/高属性值不能为空等等等等

2. AutoUtils (使用px适配)

先介绍一下这种适配方式的原理和思路:
根据设计图的标注来讲 ,10801920在使用px的情况下,假定有一个TextView宽为540px,会占据屏幕的一半,如果在其他分辨率的屏幕上如果宽度依旧为540px的话,显示的位置肯定超过或者小于屏幕一半。如果能动态的对540px进行处理、更换到对应分辨率下的数值大小既可以完成适配。
以适配480
800为例,540px的宽度在该设备上应该为:540*480/1080 = 240px,既设置240px的宽度即可完成适配。
核心的思路和算法就是

    **新的宽(高)度   =    设计图宽(高)px*实际分辨率宽(高)/设计图分辨率宽(高)**

下面贴出该类的内容和该类的使用方式

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.util.TypedValue;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class AutoUtils {

    public static int displayWidth;
    public static int displayHeight;

    private static int designWidth;
    private static int designHeight;

    private static double textPixelsRate;

    public static void setSize(Activity act, boolean hasStatusBar, int designWidth, int designHeight) {

        if (act == null || designWidth < 1 || designHeight < 1)
            return;

        Display display = act.getWindowManager().getDefaultDisplay();
        int width = display.getWidth();
        int height = display.getHeight();

        if (hasStatusBar) {
            height -= getStatusBarHeight(act);
        }
        
        AutoUtils.displayWidth = width;
        AutoUtils.displayHeight = height;

        AutoUtils.designWidth = designWidth;
        AutoUtils.designHeight = designHeight;

        double displayDiagonal = Math.sqrt(Math.pow(AutoUtils.displayWidth, 2) + Math.pow(AutoUtils.displayHeight, 2));
        double designDiagonal = Math.sqrt(Math.pow(AutoUtils.designWidth, 2) + Math.pow(AutoUtils.designHeight, 2));

        AutoUtils.textPixelsRate = displayDiagonal / designDiagonal;
    }

    public static int getStatusBarHeight(Context context) {

        int result = 0;
        try {
            int resourceId = context.getResources().getIdentifier(
                    "status_bar_height", "dimen", "android");

            if (resourceId > 0) {
                result = context.getResources().getDimensionPixelSize(resourceId);
            }

        } catch (Resources.NotFoundException e) {
            e.printStackTrace();
        }

        return result;

    }

    public static void auto(Activity act) {

        if (act == null || displayWidth < 1 || displayHeight < 1)
            return;

        View view = act.getWindow().getDecorView();
        auto(view);
    }

    public static void auto(View view) {

        if (view == null || displayWidth < 1 || displayHeight < 1)
            return;

        AutoUtils.autoTextSize(view);
        AutoUtils.autoSize(view);
        AutoUtils.autoPadding(view);
        AutoUtils.autoMargin(view);
        
        if (view instanceof ViewGroup) {
            auto((ViewGroup) view);
        }
    }
    
    private static void auto(ViewGroup viewGroup) {
        int count = viewGroup.getChildCount();
        for (int i = 0; i < count; i++) {
            View child = viewGroup.getChildAt(i);
            if (child != null) {
                auto(child);
            }
        }
    }

    public static void autoMargin(View view) {
        if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams))
            return;
        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        if (lp == null)
            return;

        lp.leftMargin = getDisplayWidthValue(lp.leftMargin);
        lp.topMargin = getDisplayHeightValue(lp.topMargin);
        lp.rightMargin = getDisplayWidthValue(lp.rightMargin);
        lp.bottomMargin = getDisplayHeightValue(lp.bottomMargin);
    }

    public static void autoPadding(View view) {
        int l = view.getPaddingLeft();
        int t = view.getPaddingTop();
        int r = view.getPaddingRight();
        int b = view.getPaddingBottom();

        l = getDisplayWidthValue(l);
        t = getDisplayHeightValue(t);
        r = getDisplayWidthValue(r);
        b = getDisplayHeightValue(b);
        
        view.setPadding(l, t, r, b);
    }

    public static void autoSize(View view) {

        ViewGroup.LayoutParams lp = view.getLayoutParams();

        if (lp == null)
            return;

        boolean isSquare = false;
        if (lp.width == lp.height) {
            isSquare = true;
        }

        if (lp.width > 0) {
            lp.width = getDisplayWidthValue(lp.width);
        }

        if (lp.height > 0) {
            lp.height = getDisplayHeightValue(lp.height);
        }

        if (isSquare) {
            if (lp.width > lp.height) {
                lp.width = lp.height;
            } else {
                lp.height = lp.width;
            }
        }
    }

    public static void autoTextSize(View view) {

        if (view instanceof TextView) {

            double designPixels = ((TextView) view).getTextSize();
            double displayPixels = textPixelsRate * designPixels;

            ((TextView) view).setIncludeFontPadding(false);
            ((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, (float) displayPixels);

        }
    }

    public static int getDisplayWidthValue(int designWidthValue) {

        if (Math.abs(designWidthValue) < 2) {
            return designWidthValue;
        }

        return designWidthValue * displayWidth / designWidth;
    }

    public static int getDisplayHeightValue(int designHeightValue) {

        if (Math.abs(designHeightValue) < 2) {
            return designHeightValue;
        }

        return designHeightValue * displayHeight / designHeight;
    }

    public static float getDisplayTextSize(float designTextSize) {
        return (float) (AutoUtils.textPixelsRate * designTextSize);
    }
}

使用方式:
在Acitivty/Fragment/Dialog的setContentView前使用

  1. AutoUtils.setSize(this, false, 750, 1344); 对应参数分别为:
    activity:用来获取当前设备的屏幕分辨率参数
    hasStatusBar :设计图中是否包含状态栏,如果true,在计算中会去除状态栏高度
    designWidth:设计图宽度
    designHeight:设计图高度

  2. Activity Fragment Dialog中
    AutoUtils.auto(this)/AutoUtils.auto(view); 即可完成适配

优点:可全局统一,方便使用,
缺点:字体采用px后,不能适配Android系统的字体缩放大小;
在绘制前进行数据计算,会产生部分性能损耗
对自定义控件某些属性需要自行适配

3. SmallestWidth适配

1. 原理

该适配方式通过屏幕的最小宽度进行适配
px=dpi/160*dp

在这里插入图片描述
在Android 代码中最小宽度获取方式:

getResources().getConfiguration().smallestScreenWidthDp)

资源文件的适配方式如下

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

在不同的宽度的资源文件中,获取R.dimen.(dp)对应不同的dp值,Android会默认筛选对应宽度的资源文件,如果资源文件中没有,则会自动向下匹配筛选,没有的时候会采用values中的默认值。

原理:根据一个基准的最小宽度分辨率,分别计算其他最小宽度下的1dp转化后对应的dp数值
直接上图:
基准最小宽度300dp下的资源数值
在这里插入图片描述
通过对基准宽度下的数值按比例扩大/缩小,进行适配,达到适配的目的。

优点:
不会有任何性能的损耗
适配范围可自由控制,不会影响其他三方库
缺点:
需要添加对应的资源文件,会略微增加apk体积;
当没有该值的资源文件时候因为会产生误差;

2.快速生成value-sw()dp
  1. Android studio 添加插件:smallestWitdh
  2. 添加成功后:在Tools-smallestWitdh 或者 option+p 中打开
  3. 在界面中输入基准dp和需要生成的dp 、sp值,如果需要额外的宽度的文件通过add添加即可
    在这里插入图片描述

4. 今日头条适配方案

通过修改系统的density适配

通过阅读源码,我们可以得知,density 是 DisplayMetrics 中的成员变量,而 DisplayMetrics 实例通过 Resources#getDisplayMetrics 可以获得,而Resouces通过Activity或者Application的Context获得
先来熟悉下 DisplayMetrics 中和适配相关的几个变量:
DisplayMetrics#density 就是上述的density
DisplayMetrics#densityDpi 就是上述的dpi

DisplayMetrics#scaledDensity 字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值

在这里插入图片描述

那么是不是所有的dp和px的转换都是通过 DisplayMetrics 中相关的值来计算的呢?

首先来看看布局文件中dp的转换,最终都是调用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 来进行转换:
在这里插入图片描述

这里用到的DisplayMetrics正是从Resources中获得的。

再看看图片的decode,BitmapFactory#decodeResourceStream方法:
在这里插入图片描述
可见也是通过 DisplayMetrics 中的值来计算的。

当然还有些其他dp转换的场景,基本都是通过 DisplayMetrics 来计算的,这里不再详述。因此,想要满足上述需求,我们只需要修改 DisplayMetrics 中和 dp 转换相关的变量即可

最终方案

下面假设设计图宽度是360dp,以宽维度来适配。

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

 // 系统的Density
 private static float sNoncompatDensity;
 // 系统的ScaledDensity
 private static float sNoncompatScaledDensity;

 public static void setCustomDensity(Activity activity, Application application) {
        DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
        if (sNoncompatDensity == 0) {
            sNoncompatDensity = displayMetrics.density;
            sNoncompatScaledDensity = displayMetrics.scaledDensity;
            // 监听在系统设置中切换字体
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(Configuration newConfig) {
                    if (newConfig != null && newConfig.fontScale > 0) {
                        sNoncompatScaledDensity=application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                @Override
                public void onLowMemory() {

                }
            });
        }
        // 此处以360dp的设计图作为例子
        float targetDensity=displayMetrics.widthPixels/360;
        float targetScaledDensity=targetDensity*(sNoncompatScaledDensity/sNoncompatDensity);
        int targetDensityDpi= (int) (160 * targetDensity);
        displayMetrics.density = targetDensity;
        displayMetrics.scaledDensity = targetScaledDensity;
        displayMetrics.densityDpi = targetDensityDpi;

        DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.scaledDensity = targetScaledDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }

这样,宽度适配就已经完成啦,只需要在Activity中调用就行了,必须在setContentView()之前!
如果需要适配高度,今日头条指出只要按照同样的方法做高度适配就可以了!

今日头条的适配方式进阶版本

由JessYan开发者开源的项目。
在此就不做更多的描述,下面放上作者的地址和github链接

附上作者文章链接:https://www.jianshu.com/u/1d0c0bc634db
附上作者的github地址:https://github.com/JessYanCoding/AndroidAutoSize

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值