屏幕适配全方位解析

1.屏幕适配概念

而随着支持Android系统的设备(手机、平板、电视、手表)的增多,设备碎片化、品牌碎片化、系统碎片化、传感器碎片化和屏幕碎片化的程度也在不断地加深。而咱们今天要探讨的,则是对咱们开发影响比较大的——屏幕的碎片化。android

下面这张图是Android屏幕尺寸的示意图,在这张图里面,蓝色矩形的大小表明不一样尺寸,颜色深浅则表明所占百分比的大小。面试

下面是IOS的canvas

经过对比能够很明显知道adnroid的屏幕到底有多少种了吧。而苹果只有5种包括如今最新的刘海屏ide

那么想要对屏幕适配的相关处理方案有必定的本身的心得,那么首先咱们须要了解关于android屏幕的必定基础布局

1.屏幕适配基础

那么下面是我给你们写的一个屏幕适配基础的思惟导图,基本为一个基础篇的大纲,这里我不会在课上很是详细的给你们去过,就所有体如今简书当中this

那么屏幕适配相关概念上咱们须要掌握最基础的3点
那么相对基础的内容是给段位比较低的同窗,高段位可选择跳过spa

1.什么是屏幕尺寸,屏幕分辨率,屏幕像素密度

屏幕尺寸指的是:.net

分辨率:翻译

屏幕像素密度(DPI<Dots Per Inch>)
指每一英寸长度中,可显示输出的像素个数,
DPI的数字受屏幕尺寸和分辨率所影响,DPI能够经过计算所得3d

上述内容在于扫盲..毕竟仍是有不清楚的同窗,而DPI跟下面内容结合比较密切因此啰嗦了两句

2.什么是dp,dip,sp,px?它们之间的关系?

px:构成图像的最小单位
dip(重点):Desity Independent pixels的缩写,即密度无关像素
android内部在识别图像像素时以160dpi为基准,1dip=1px或1dp=1px
例:在下列两台设备上使用DP进行操做
480 * 320 160dpi 那么这台机器上的1DP会被翻译成1px
800 * 480 240dpi 而这台机器上的1DP会被翻译成1.5px
也就是说当前咱们设备的DP是由android给予的基础标准按比例进行翻译的,这也是为何咱们用DP能解决一部分适配的缘由

3.mdpi,hdpi,xdpi,xxdpi,xxxdpi?如何计算和区分?

名称                 像素密度范围         图片大小
  mdpi                 120dp~160dp         48×48px
  hdpi                 160dp~240dp         72×72px
  xhdpi                240dp~320dp         96×96px
  xxhdpi               320dp~480dp         144×144px
  xxxhdpi              480dp~640dp         192×192px

在Google官方开发文档中,说明了 mdpi:hdpi:xhdpi:xxhdpi:xxxhdpi=2:3:4:6:8 的尺寸比例进行缩放。例如,一个图标的大小为48×48dp,表示在mdpi上,实际大小为48×48px,在hdpi像素密度上,实际尺寸为mdpi上的1.5倍,即72×72px,以此类推,能够继续日后增长,不过通常状况下已经够用了,这种用来去适配手机和平板之间的图形问题

2.屏幕适配基础篇(常识,见思惟导图,这里只详细讲一下限定符)

2.1使用 "wrap_content" 和 "match_parent"
2.2相对布局控制屏幕
2.3. .9图的应用
上面三个都是最基本的android使用,咱们只须要在日常应用是注意到就好了,这里不详细去讲

2.4.限定符

咱们在作屏幕的适配时在屏幕 尺寸相差不大的状况下,dp可使不一样分辨率的设备上展现效果类似。可是在屏幕尺寸相差比较大的状况下(平板),dp就失去了这种效果。因此须要如下的限定符来约束,采用多套布局,数值等方式来适配。

那么其实所谓的限定符就是android在进行资源加载的时候会按照屏幕的相关信息对文件夹对应的名字进行识别,而这些特殊名字就是咱们的限定符

限定符分类:
    屏幕尺寸    
        small   小屏幕
        normal  基准屏幕
        large   大屏幕
        xlarge  超大屏幕
    屏幕密度
        ldpi    <=120dpi
        mdpi    <= 160dpi
        hdpi    <= 240dpi
        xhdpi   <= 320dpi
        xxhdpi  <= 480dpi
        xxhdpi  <= 640dpi(只用来存放icon)
        nodpi   与屏幕密度无关的资源.系统不会针对屏幕密度对其中资源进行压缩或者拉伸
        tvdpi   介于mdpi与hdpi之间,特定针对213dpi,专门为电视准备的,手机应用开发不须要关心这个密度值.
    屏幕方向    
        land    横向
        port    纵向
    屏幕宽高比   
        long    比标准屏幕宽高比明显的高或者宽的这样屏幕
        notlong 和标准屏幕配置同样的屏幕宽高比

2.4.1使用尺寸限定符:

当咱们要在大屏幕上显示不一样的布局,就要使用large限定符。例如,在宽的屏幕左边显示列表右边显示列表项的详细信息,在通常宽度的屏幕只显示列表,不显示列表项的详细信息,咱们就可使用large限定符。
res/layout/main.xml 单面板:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 列表 -->
<fragment android:id="@+id/headlines"
          android:layout_height="fill_parent"
          android:name="com.example.android.newsreader.HeadlinesFragment"
          android:layout_width="match_parent" />
</LinearLayout>

res/layout-large/main.xml 双面板:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<!-- 列表 -->
<fragment android:id="@+id/headlines"
          android:layout_height="fill_parent"
          android:name="com.example.android.newsreader.HeadlinesFragment"
          android:layout_width="400dp"
          android:layout_marginRight="10dp"/>
<!-- 列表项的详细信息 -->
<fragment android:id="@+id/article"
          android:layout_height="fill_parent"
          android:name="com.example.android.newsreader.ArticleFragment"
          android:layout_width="fill_parent" />
</LinearLayout>

若是这个程序运行在屏幕尺寸大于7inch的设备上,系统就会加载res/layout-large/main.xml 而不是res/layout/main.xml,在小于7inch的设备上就会加载res/layout/main.xml。

须要注意的是,这种经过large限定符分辨屏幕尺寸的方法,适用于android3.2以前。在android3.2以后,为了更精确地分辨屏幕尺寸大小,Google推出了最小宽度限定符。

2.4.2使用最小宽度限定符

最小宽度限定符的使用和large基本一致,只是使用了具体的宽度限定。
res/layout/main.xml,单面板(默认)布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<fragment android:id="@+id/headlines"
          android:layout_height="fill_parent"
          android:name="com.example.android.newsreader.HeadlinesFragment"
          android:layout_width="match_parent" />
</LinearLayout>

res/layout-sw600dp/main.xml,双面板布局: Small Width 最小宽度

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<fragment android:id="@+id/headlines"
          android:layout_height="fill_parent"
          android:name="com.example.android.newsreader.HeadlinesFragment"
          android:layout_width="400dp"
          android:layout_marginRight="10dp"/>
<fragment android:id="@+id/article"
          android:layout_height="fill_parent"
          android:name="com.example.android.newsreader.ArticleFragment"
          android:layout_width="fill_parent" />
</LinearLayout>

这种方式是不区分屏幕方向的。这种最小宽度限定符适用于android3.2以后,因此若是要适配android所有的版本,就要使用large限定符和sw600dp文件同时存在于项目res目录下。

这就要求咱们维护两个相同功能的文件。为了不繁琐操做,咱们就要使用布局别名。

2.4.3使用布局别名
res/layout/main.xml: 单面板布局
res/layout-large/main.xml: 多面板布局
res/layout-sw600dp/main.xml: 多面板布局
因为后两个文具文件同样,咱们能够用如下两个文件代替上面三个布局文件:

res/layout/main.xml 单面板布局
res/layout/main_twopanes.xml 双面板布局

而后在res下创建
res/values/layout.xml、
res/values-large/layout.xml、
res/values-sw600dp/layout.xml三个文件。

默认布局
res/values/layout.xml:

<resources>
    <item name="main" type="layout">@layout/main</item>
</resources>

Android3.2以前的平板布局
res/values-large/layout.xml:

<resources>
    <item name="main" type="layout">@layout/main_twopanes</item>
</resources>

Android3.2以后的平板布局
res/values-sw600dp/layout.xml:

<resources>
    <item name="main" type="layout">@layout/main_twopanes</item>
</resources>

这样就有了main为别名的布局。
在activity中setContentView(R.layout.main);

这样,程序在运行时,就会检测手机的屏幕大小,若是是平板设备就会加载res/layout/main_twopanes.xml,若是是手机设备,就会加载res/layout/main.xml 。咱们就解决了只使用一个布局文件来适配android3.2先后的全部平板设备。

2.4.4使用屏幕方向限定符
若是咱们要求给横屏、竖屏显示的布局不同。就可使用屏幕方向限定符来实现。
例如,要在平板上实现横竖屏显示不用的布局,能够用如下方式实现。
res/values-sw600dp-land/layouts.xml:横屏

<resources>
    <item name="main" type="layout">@layout/main_twopanes</item>
</resources>

res/values-sw600dp-port/layouts.xml:竖屏、

<resources>
    <item name="main" type="layout">@layout/main</item>
</resources>

那么上述是最基本的屏幕适配的解决方案
这里找到一个神人给官方适配方案作的翻译推给你们参考
https://blog.csdn.net/wzy_1988/article/details/52932875

3.屏幕适配解决方案:

基础篇结束以后,咱们市场上最经常使用的解决方案我给你们总结了两种
1.经过自定义布局组件来完成

有听过我公开课的同窗应该知道我当时写了一套,其核心原理是根据一个参照分辨率进行布局,而后再各个机器上提取当前机器分辨率换算出系数以后,而后再经过从新测量的方式来达到适配的效果,这一套方案基本能适用于95以上的机型,那么今天到时候再加上刘海屏的适配就OK了。
下面是代码,

/**
   * Created by barry on 2018/6/7.
   */
public class ScreenAdaptationRelaLayout extends RelativeLayout {
public ScreenAdaptationRelaLayout(Context context) {
    super(context);
}

public ScreenAdaptationRelaLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public ScreenAdaptationRelaLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

static boolean isFlag = true;

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    if(isFlag){
        int count = this.getChildCount();
        float scaleX =  UIUtils.getInstance(this.getContext()).getHorizontalScaleValue();
        float scaleY =  UIUtils.getInstance(this.getContext()).getVerticalScaleValue();

        Log.i("testbarry","x系数:"+scaleX);
        Log.i("testbarry","y系数:"+scaleY);
        for (int i = 0;i < count;i++){
            View child = this.getChildAt(i);
            //表明的是当前空间的全部属性列表
            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
            layoutParams.width = (int) (layoutParams.width * scaleX);
            layoutParams.height = (int) (layoutParams.height * scaleY);
            layoutParams.rightMargin = (int) (layoutParams.rightMargin * scaleX);
            layoutParams.leftMargin = (int) (layoutParams.leftMargin * scaleX);
            layoutParams.topMargin = (int) (layoutParams.topMargin * scaleY);
            layoutParams.bottomMargin = (int) (layoutParams.bottomMargin * scaleY);
        }
        isFlag = false;
    }

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}
}
public class UIUtils {

private Context context;

private static UIUtils utils ;

public static UIUtils getInstance(Context context){
    if(utils == null){
        utils = new UIUtils(context);
    }
    return utils;
}

//参照宽高
public final float STANDARD_WIDTH = 720;
public final float STANDARD_HEIGHT = 1232;

//当前设备实际宽高
public float displayMetricsWidth ;
public float displayMetricsHeight ;

private  final String DIMEN_CLASS = "com.android.internal.R$dimen";

private UIUtils(Context context){
    this.context = context;
    //
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

    //加载当前界面信息
    DisplayMetrics displayMetrics = new DisplayMetrics();
    windowManager.getDefaultDisplay().getMetrics(displayMetrics);

    if(displayMetricsWidth == 0.0f || displayMetricsHeight == 0.0f){
        //获取状态框信息
        int systemBarHeight = getValue(context,"system_bar_height",48);

        if(displayMetrics.widthPixels > displayMetrics.heightPixels){
            this.displayMetricsWidth = displayMetrics.heightPixels;
            this.displayMetricsHeight = displayMetrics.widthPixels - systemBarHeight;
        }else{
            this.displayMetricsWidth = displayMetrics.widthPixels;
            this.displayMetricsHeight = displayMetrics.heightPixels - systemBarHeight;
        }

    }
}

//对外提供系数
public float getHorizontalScaleValue(){
    return displayMetricsWidth / STANDARD_WIDTH;
}

public float getVerticalScaleValue(){

    Log.i("testbarry","displayMetricsHeight:"+displayMetricsHeight);
    return displayMetricsHeight / STANDARD_HEIGHT;
}

public int getValue(Context context,String systemid,int defValue) {

    try {
        Class<?> clazz = Class.forName(DIMEN_CLASS);
        Object r = clazz.newInstance();
        Field field = clazz.getField(systemid);
        int x = (int) field.get(r);
        return context.getResources().getDimensionPixelOffset(x);

    } catch (Exception e) {
       return defValue;
    }
}

}

2.给各个分辨率单独适配,res,dimens里设置各个对应的px,再统一调用,由系统筛选。

这种方式比较久远了,可是确实仍是有不少项目在使用到这种方式
其原理就是据设备屏幕的分辨率各自写一套dimens.xml文件,而后根据一个基准分辨率(例如720x1080),将宽度分红720份,取值为1px——720px,将高度分红1080份,取值为1px——1080px。生成各自dimens.xml文件对应的值。
可是今天我根据这个方法,在这个方案的基础之上给你们作了一次改变,运用以前所见的DP的概念,结合以前讲的限定符,用DP来升级了这种方案,dp适配原理与px适配同样,区别就在于px适配是根据屏幕分辨率,即拿px值等比例缩放,而dp适配是拿dp值来等比缩放而已。
既然原理都同样,都须要多套dimens.xml文件,为何说dp适配就比px适配好呢?
由于px适配是根据屏幕分辨率的,Android设备分辨率一大堆,并且还要考虑虚拟键盘。而dp适配不管手机屏幕的像素多少,密度比值多少,80%的手机的最小宽度dp值(widthPixels / density)都为360dp,这样就大大减小了dimens.xml文件
PS:(如今基本上手机的dpi都在350+以上 那么按最低算 350/160=2.1 那么360 2.1 = 720+ 基本上手机的分辨率都会在360dp以内 上面例子19201080的状况 500/160=3.125 那么 360*3.125=1125其实也在360以内)
那么传统作法:

改良后的作法:

获取最小宽度获取以下:

DisplayMetrics dm = new DisplayMetrics();

    getWindowManager().getDefaultDisplay().getMetrics(dm);

    int widthPixels = dm.widthPixels;

    float density = dm.density;

    float widthDP = widthPixels / density;

因此经过这种两种形式的结合可以达到咱们总体适配任意机型的目的

做者:Barry
原文连接:https://www.jianshu.com/p/0586c7e7e212

阅读更多

适配不一样尺寸屏幕,自动拉伸位图9.图片的使用|处理

适配不一样尺寸屏幕几个关键点分享

屏幕适配之尺寸的相关概论《一》

2018 Android面试心得,已拿到offer

2018 Android面试心得,已拿到offer

相关文章
相关标签/搜索