多分辨率适配的思考

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

设计师应该按照哪个比例出图

  • 设计师出图总是有对标的设备的,比如按照iphone6s或者按照iphone13mini,这些设备的宽高和尺寸是固定的,因此dpi也是固定的。
  • 举个例子,公司按照iphone13mini的标准出图,宽高是375 * 812 ,屏幕尺寸是5.42英寸
//一倍图导出
ppi = 根号(375*375+812*812) / 5.42 = 164

//按照三倍导出则是宽高分别为 1125*2436
ppi = 495 
复制代码

再对比不同的drawable目录所需要的资源文件,我们可以很方便得知,按照三倍导出的图需要放入到xxhdpi的目录下。而按照一倍图导出的放入到mdpi下。 image.png

  • 目前设计师一般都是用figma出图,选择按标准dpi,也就是160dpi出图,在导出2倍图,3倍图时,刚好可以放到对应的xhpid,xxhdpi目录下,也算是一种取巧。

资源是怎么被使用的?

资源查找规则

android系统会根据设备本身的dpi来寻找最合适的图片加载,比如设备本身是xhdpi,则它的查找资源顺序为

xhdpi-xxhdpi-xxxhdpi-nodpi-hdpi-mdpi-ldpi

资源没命中会如何?

  • 当xhdpi的设备从xxhdpi找到某张图时,它认为图的密度高于了设备本身的密度,因此会对图片进行缩小。
  • 同理如果高密度的设备从低密度的文件夹找到图片时,图片会被放大
  • 图片缩放的比例为 destinateDpi/deviceDpi

分辨率该如何适配?

分辨率适配的根本目的

  • 分辨率适配的根本目的是为了让不同的设备对同样一张图“看起来感官”一样。也就是如果是固定宽度的app,要求不同的按钮,图片在不同设备上看起来占比是一样的。

  • 如下图所示,按钮A与页面的比例都是50%。由于导出的图片的尺寸是一致的,比如说都是50x50,因此需要对图片做放大才能达到目的。右边的图片我们需要放大到75x75.

image.png

如何适配

为了使得图片看起来一致,我们需要按照比例去放大图片,问题转化为如何计算图片放大的比例。

假定设备的宽高为wScreen,hScreen,设计稿宽高为wDesign,hDesign

宽高比缩放

最简单纯粹的方案为:

scaleFactorW =  wScreen/wDesign
scaleFactorH =  hScreen/hDesign
复制代码

由于设备的比例多种多样,比如16:9 , 2:1 , 4:3,如果按照这样计算,scaleFactorW和scaleFactorH的大小不一样,图片会被拉伸。

为了解决拉伸问题,我们针对不同的业务类型,选择固定某个值作为参考,也就是固定宽缩放或者固定高缩放。

比如在一些新闻资讯类app,高度是不敏感的,宽度很敏感,就可以选择固定宽缩放。因此可以得到

scaleFactor = wScreen/wDesign
复制代码

直接设置宽高可以吗?

既然我们得到最终的适配测试是让宽高缩放一个固定的比例,那我们可以直接写死一个值吗?

显然是不行的。我们在xml去写某个ImageView大小的时候,很多时候都写了一个固定的数字,比如30dp,30px,对于不同的设备,我们缩放完后算出的大小是不一样的。

怎么办呢。要解决这个问题,我们要看下系统对于dp的解析逻辑。

dp的解析逻辑

这个dp的解析逻辑,网上有很多的分析代码,最终都指向一出,即TypedValue.applyDimension

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转化出来的公式

//px与dp的关系 
pxScreen = dpScreen * density
//可知 density的含义为像素与dp的比值
density = pxScreen / dpScreen
//对于宽度来说
density = wScreen/dpScreen
复制代码

根据这个公式density=wScreen/dpScreen,我们可以理解成,density为屏幕宽度包含了多少个dp,也就是1dp实际占用了屏幕多少的像素。

根据我们的缩放公式scaleFactor=wScreen/wDesign,缩放的最终目的是让设计稿的宽度在不同设备上看起来一样。当我们的设计稿的宽度dp固定时,希望这个固定的dp能够刚好均匀的占满整个屏幕的宽度。所以density计算出来就是屏幕的宽度/设计稿的dp,就是我们要缩放的比例。也就是

scaleDensity = wScreen / dpDeisgn
复制代码

代码

 public class AutoSizeHelper {

    private boolean isInited = false;
    private float sNoncompatDensity;
    private float sNoncompatScaledDensity;
    private float sTargetDensity;


    private static AutoSizeHelper sInstance;

    private AutoSizeHelper() {
    }

    public synchronized static AutoSizeHelper getInstance() {
        if (sInstance == null) {
            sInstance = new AutoSizeHelper();
        }
        return sInstance;
    }

    public void init(Application application) {
        DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
        sNoncompatDensity = appDisplayMetrics.density;
        sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;

        // portrait下短边占屏幕适配100%权重,landscape下长短边各占适配50%的权重
        if (OXConstant.APP_MODE == OXConstant.AppMode.PORTRAIT) {
            sTargetDensity = Kits.Dimens.getRawScreenShorter() * 1F / OXConstant.DESIGN_SHORTER;
        } else {
            sTargetDensity = Kits.Dimens.getRawScreenLonger() * 0.5F / OXConstant.DESIGN_LONGER + Kits.Dimens.getRawScreenShorter() * 0.5F / OXConstant.DESIGN_SHORTER;
        }

        isInited = true;
    }

    public boolean isInited() {
        return isInited;
    }

    /**
     * 为了保证density适配的有效性,并且在WebView初始化、屏幕旋转等多种情况下density被动重置后依然能正确重设回来
     * 请在getResource时重设density
     *
     * @param displayMetrics
     */
    public void setDisplayMetrics(DisplayMetrics displayMetrics) {
        if (displayMetrics == null || displayMetrics.density == sTargetDensity) {
            return;
        }

        int targetDensityDpi = (int) (160 * sTargetDensity);
        float targetScaledDensity = sTargetDensity * (sNoncompatScaledDensity / sNoncompatDensity);

        displayMetrics.density = sTargetDensity;
        displayMetrics.scaledDensity = targetScaledDensity;
        displayMetrics.densityDpi = targetDensityDpi;
    }
}
复制代码

猜你喜欢

转载自juejin.im/post/7067341369936707620