Android 关于屏幕的一些事儿

转载请标明出处:
http://blog.csdn.net/xuehuayous/article/details/50596463;
本文出自:【Kevin.zhou的博客】

前言:android数以千计的分辨率令我们开发者头痛不已,那么我们能不能解开它的面纱来欣赏一下它的本质呢?以下就带大家进行这么一场美妙的观光。


一、概述


    Android的屏幕适配是一个比较受关注的问题,再加上UI、UE一般是按照IOS出一套然后Android也是对照着去做,给美工小妹妹想要讲清楚dp是一件比登天还难的事情。先来了解下这几个点位之间的关系。


二、 PX、PT、PPI、DPI、DP


术语
说明
备注
PX
(pixel),像素,屏幕上显示数据的最基本的点

PT
(point), 点,印刷行业常用单位
1pt=1/72英寸
PPI
(pixel per inch),每英寸像素数

DPI
(dot per inch),每英寸点数

DP
dip(Density-independent pixel), 设备独立像素
1dp=160dpi时1px长度

    其中px, pt, dp为长度单位,ppi和dpi为密度单位。安卓端屏幕大小各不相同,根据其像素密度,分为以下几种规格:

Android 关于屏幕的一些事儿_第1张图片

    dp为安卓开发时的长度单位,根据不同的屏幕分辨率,与px有不同的对应关系。


三、获取屏幕宽高


    获取屏幕的宽高是我们开发中经常遇到的问题,而且相信大家都已经非常熟悉,最常用的为以下两种:
public static int getScreenHeight1(Activity activity) {
    return activity.getWindowManager().getDefaultDisplay().getHeight();
}
public static int getScreenHeight2(Activity activity) {
    DisplayMetrics displayMetrics = new DisplayMetrics();
    activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    return displayMetrics.heightPixels;
}
    其实以上两种方式是一样的,只不过第二种是把信息封装到 DesplayMetrics中,再从 DesplayMetrics得到数据。
在  Android 3.2(Api 13) 之后又提供了如下的一个方法,将数据封装到Point中,然后返回宽度高度信息。
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public static int getScreenHeight3(Activity activity) {
    Point point = new Point();
    activity.getWindowManager().getDefaultDisplay().getSize(point);
    return point.y;
}
    在 Android 4.2(Api17) 之后提供了如下方法,与第三种类似也是将数据封装到Point中,然后返回款高度信息。

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static int getScreenHeight4(Activity activity) {
    Point realSize = new Point();
    activity.getWindowManager().getDefaultDisplay().getRealSize(realSize);
    return realSize.y;
}

    那么我们运行一把,看下获取的具体信息,以下为MI3和海尔平板(S1001LS)获取的数据:


Android 关于屏幕的一些事儿_第2张图片


    在MI3上四个方法获取到的数据都为1920px,但是在海尔平板(S1001LS)上面前三个为1848px,最后一个为1920px,出现了分歧,我们看官方说分辨率是多少:

Android 关于屏幕的一些事儿_第3张图片


    我擦,吓我一跳,原来我天天扔的开机发这么叼,三千多呢。也验证了他高度1920px。那么看开我们写的四种获取屏幕高度的方法前三种都是有问题的,根本不是获取到的屏幕高度。其实是由于包不包含底部的导航栏的原因。

    由于getRealSize()这个方法是是在 Android 4.2(Api17) 之后提供的,那是不是意味着之前的版本我们就不能得到确切的屏幕分辨率呢?我们到源码来看下:
Android 关于屏幕的一些事儿_第4张图片


    通过查找发现,在Android 4.0(Api14)就提供了getRealSize()这个方法,只不过是系统隐藏了,我们不能直接去调用。那么能不能通过反射的方式来使用呢?

public static int getScreenHeight5(Activity activity) {
    Point realSize = new Point();
    Display display = activity.getWindowManager().getDefaultDisplay();
    try {
        Display.class.getMethod("getRealSize", Point.class).invoke(display, realSize);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return realSize.y;
}

和我们预期的一样在Android 4.0也获取到了数据。

其实系统在Android 3.2(Api13)开始加入了如下方法,只不过一直是隐藏的API:
/**
 * Gets the raw width of the display, in pixels.
 * <p>
 * The size is adjusted based on the current rotation of the display.
 * </p>
 * @hide
 */
public int getRawHeight() {
int h = getRawHeightNative();
    if (DEBUG_DISPLAY_SIZE) Slog.v(
            TAG, "Returning raw display height: " + h);
    return h;
}
private native int getRawWidthNative();
当然我们可以通过反射的方式来调用它:

public static int getScreenHeight6(Activity activity) {
    int heightPixels = 0;
    Display display = activity.getWindowManager().getDefaultDisplay();
    try {
        heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return heightPixels;
}

Android 版本
版本号
getRawHeight()
getRealSize(Point p)
Android 3.2
13
包含(隐藏)

Android 4.0,4.0.1,4.0.2
14
包含(隐藏)
包含(隐藏)
Android 4.0.3,4.0.4
15
包含(隐藏)
包含(隐藏)
Android 4.1,4.1.1
16
包含(隐藏)
包含(隐藏)
Android 4.2,4.2.2
17
包含(隐藏)
包含
... ...
... ...
... ...
... ...

综上,我们可以得到一个比较完整的获取系统屏幕高度的方法:

public int getRealHeight(Activity activity) {
    int heightPixels = 0;
    Display display = activity.getWindowManager().getDefaultDisplay();
    final int VERSION = Build.VERSION.SDK_INT;

    if(VERSION < 13) {
        display.getHeight();
    }else if (VERSION == 13) {
        try {
            heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
        } catch (Exception e) {
        }
    } else if (VERSION >= 14 && VERSION < 17) {
        Point realSize = new Point();
        try {
            Display.class.getMethod("getRealSize", Point.class).invoke(display, realSize);
            heightPixels = realSize.y;
        } catch (Exception e) {
        }
    } else {
        Point realSize = new Point();
        display.getRealSize(realSize);
        heightPixels = realSize.y;
    }
    return heightPixels;
}
简化一下就是:

public int getRealHeight(Activity activity) {
    int heightPixels = 0;
    Display display = activity.getWindowManager().getDefaultDisplay();
    final int VERSION = Build.VERSION.SDK_INT;

    if(VERSION < 13) {
        display.getHeight();
    }else if (VERSION == 13) {
        try {
            heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
        } catch (Exception e) {
        }
    } else {
        Point realSize = new Point();
        try {
            Display.class.getMethod("getRealSize", Point.class).invoke(display, realSize);
            heightPixels = realSize.y;
        } catch (Exception e) {
        }
    }
    return heightPixels;
}

四、获取屏幕尺寸


    以上我们为什么费了那么大劲非要搞到屏幕的真实高度呢?我们又没有办法控制下方的导航栏,得到真实高度也没什么卵用,其实我们获取真实的屏幕高度是为了计算屏幕的高度。那么屏幕的物理尺寸怎么去计算呢?

1. 屏幕物理尺寸是屏幕对角线的长度,单位英寸;
2. 屏幕的像素点密度(ppi) ≠ dp;
3. 屏幕物理宽度 = width / xppi;
4. 屏幕物理高度 = height / yppi;

我们发现 DisplayMetrics 中有如下两个变量:

/**
 * The exact physical pixels per inch of the screen in the X dimension.
 */
public float xdpi;
/**
 * The exact physical pixels per inch of the screen in the Y dimension.
 */
public float ydpi;
没错,这就是我们要找的在宽度和高度上的ppi(每英寸内的像素点数目)。

/**
 * 获取屏幕宽度ppi
 *
 * @param activity
 * @return 屏幕宽度ppi
 */
public static float getWidthPpi(Activity activity) {
    DisplayMetrics displayMetrics = new DisplayMetrics();
    activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    return displayMetrics.xdpi;
}
/**
 * 获取屏幕高度ppi
 *
 * @param activity
 * @return 屏幕高度ppi
 */
public static float getHeightPpi(Activity activity) {
    DisplayMetrics displayMetrics = new DisplayMetrics();
    activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    return displayMetrics.ydpi;
}
那么获取宽高的物理尺寸也就简单了:

/**
 * 获取屏幕宽度物理尺寸
 *
 * @param activity
 * @return
 */
public static float getWidthInch(Activity activity) {
    int realWidth = getRealWidth(activity);
    return (float)realWidth / getWidthPpi(activity);
}
/**
 * 获取屏幕高度物理尺寸
 *
 * @param activity
 * @return
 */
public static float getHeightInch(Activity activity) {
    int realHeight = getRealHeight(activity);
    return (float)realHeight / getHeightPpi(activity);
}
根据勾股定理,宽度和高度的尺寸都知道了,对角线的长度就是长度平方加上高度平方再开平方:

/**
 * 获取屏幕物理尺寸
 *
 * @param activity
 * @return 屏幕物理尺寸
 */
public static float getScreenInch(Activity activity) {
    return (float)Math.sqrt(Math.pow(getWidthInch(activity), 2) + Math.pow(getHeightInch(activity), 2));
}

五、获取长宽DP

    
    知道DP和PPI的关系,以及屏幕的长宽值之后想要获取屏幕长度和宽度上的总dp就比较简单了。首先通过代码得到dp和ppi(dpi)的对应关系:

/**
 * 获取屏幕密度
 *
 * @return
 */
public static float getScreenDensity(Activity activity) {
    DisplayMetrics displayMetrics = new DisplayMetrics();
    activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    return displayMetrics.density;
}
然后用屏幕宽高除以 屏幕密度就是宽高上的dp数目:

/**
 * 获取屏幕可操作区域宽度dp数目
 *
 * @param activity
 * @return
 */
public static float getScreenWidthDp(Activity activity) {
    return getScreenWidth(activity) / getScreenDensity(activity);
}
/**
 * 获取屏幕高度可操作区域dp数目
 *
 * @param activity
 * @return
 */
public static float getScreenHeightDp(Activity activity) {
    return getScreenHeight(activity) / getScreenDensity(activity);
}
/**
 * 获取屏幕真实宽度dp数目
 *
 * @param activity
 * @return
 */
public static float getRealWidthDp(Activity activity) {
    return getRealWidth(activity) / getScreenDensity(activity);
}
/**
 * 获取屏幕真实高度dp数目
 *
 * @param activity
 * @return
 */
public static float getRealHeightDp(Activity activity) {
    return getRealHeight(activity) / getScreenDensity(activity);
}

六、判断是手机还是平板


    有时候在一套代码跑在手机和平板上,所以就要根据是平板还是pad来设置不同的布局。当然手机和平板公用一套代码显然不是一个好的方案,也确实能减小项目的开发维护成本。
以下为几种方案:
1. 判断设备是否具备通话功能
2. 判断设备是否大于6英寸
3. 判断设备是否为大尺寸

第一种方案不太有效,因为现在好多平板也是可以打电话的。判断设备是否大于6英寸是一个常用的做法:

/**
 * 判断屏幕是否大于6英寸
 *
 * @param activity
 * @return
 */
public static boolean isMoreThan6Inch(Activity activity) {
    return getScreenInch(activity) >= 6.0;
}
第三种判断是否为大尺寸设备:
/**
 * 判断设备是否为大尺寸屏幕
 *
 * @param context
 * @return
 */
public static boolean isScreenSizeLarge(Context context) {
    return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
我们开看下SCREENLAYOUT_SIZE_LARGE 是怎么定义的:

/** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK}
 * value indicating the screen is at least approximately 480x640 dp units,
 * corresponds to the
 * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenSizeQualifier">large</a>
 * resource qualifier.
 * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting
 * Multiple Screens</a> for more information. */
public static final int SCREENLAYOUT_SIZE_LARGE = 0x03;
可以看到是判断的屏幕高度和宽度的dp数目,他要求最小为 480X640 dp,我觉得最好这两个都满足才是平板:

/**
 * 判断设备是否为平板
 *
 * @param activity
 * @return true 平板;
 *          false 手机;
 */
public static boolean isTablet(Activity activity) {
    return isMoreThan6Inch(activity) && isScreenSizeLarge(activity);
}

七、源码及示例

   
给大家提供一个github的地址: Android-Utils 中的 ScreenUtil.java Adroid-Utils是我想把之前的工具类整理下,还在完善中,大家有好的想法可以给我留言,共同进步!
    另外,欢迎 star or f**k me on github! 


结语:

    
    其实我们在开发中还比较在意的一个问题是:


Android 关于屏幕的一些事儿_第5张图片


    就是我要写多少才他么是真正的占屏幕宽度的一半!!! 后续打算做个工具来处理这些烦人的东西。
要过年了还他么感冒了,加上一些乱七八糟的事情,最近好不爽。牢骚发完了,该干嘛干嘛去!

你可能感兴趣的:(android,屏幕,屏幕物理尺寸,手机平板判断)