美文网首页
关于Android的屏幕适配

关于Android的屏幕适配

作者: ei_chinn | 来源:发表于2016-12-24 22:41 被阅读0次

    对于Android的屏幕适配,似乎所有的Android开发人员都知道这么一条准则:使用dp而不是使用px。但是当面对着所有的标注都是以px为单位的设计图时,应该怎样将其转换为dp,使得在(几乎)所有的屏幕上都能显示出相同的效果?最近的我却有点茫然,至今仍未找到理想的答案。下面是我关于这个问题的一些思考与资料搜索,整理一下写出来,期望能梳理出一点头绪。

    px = dp * (dpi / 160)

    关于这条式子其实直到不久之前我的理解都还是错误的,我一直把它当作是一条关于px和dp这两个单位的转换公式,如同1cm=10mm。所以对于“在dpi=160的屏幕上,1px=1dp”这句话我十分的不解:那就是说,在dpi=320的屏幕上,1px=2dp即1dp = 1/2 px?1dp对应1/2个像素点?这不就是与dp的定义中的“屏幕密度越大,1dp对应的像素点越多”相悖了吗?
    其实正确的理解应该是:这是一条px与dp之间的关系表达式。如同y=ax,当a确定的时候,由x的值可以得到y的值,反之亦然。这样上面的疑问就能解答了:在dpi=320的屏幕上,当设计图上的一个标注的px值为1时,对应的dp值应为0.5(1 = 0.5 * (320 / 160))。

    物理dpi与系统dpi

    dpi计算公式

    根据上面的dpi的计算公式,以我手头上的华为荣耀6plus测试机(1920 * 1080,5.5'')为例,其dpi应该约等于400,即在这部手机上1dp对应着2.5(400/160)个像素点。
    我们可以将手机屏幕信息和一个长度为100dp的Button所占的像素打印出来验证一下。

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_screen"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.eichinn.practice.ScreenActivit">
        <Button
            android:id="@+id/btn"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:text="100dp"/>
    
    </RelativeLayout>
    
    public class ScreenActivity extends AppCompatActivity {
        private Button btn;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_screen);
            
            Log.i("tag", "widthPixels: " + getRealWight(this));
            Log.i("tag", "heightPixels: " + getRealHeight(this));
            Log.i("tag", "generalizedDpi: " + getGeneralizedDpi(this));
    
            btn = (Button) findViewById(R.id.btn);
            btn.post(new Runnable() {
                @Override
                public void run() {
                    Log.i("tag", "100dp == " + btn.getWidth() + "px");
                }
            });
        }
    
        public static float getGeneralizedDpi(Activity activity) {
            DisplayMetrics dm = new DisplayMetrics();
            activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
            return dm.density;
        }
    
        public static int getRealWight(Activity activity) {
            WindowManager wm = activity.getWindowManager();
            Display display = wm.getDefaultDisplay();
            int screenWidth = 0;
    
            if (Build.VERSION.SDK_INT >= 17) {
                Point size = new Point();
                display.getRealSize(size);
                screenWidth = size.x;
            } else if (Build.VERSION.SDK_INT >= 14) {
                try {
                    screenWidth = (Integer) Display.class.getMethod("getRawWidth").invoke(display);
                } catch (Exception e) {
                    DisplayMetrics dm = new DisplayMetrics();
                    display.getMetrics(dm);
                    screenWidth = dm.widthPixels;
                }
            }
            return screenWidth;
        }
        public static int getRealHeight(Activity activity) {
            WindowManager wm = activity.getWindowManager();
            Display display = wm.getDefaultDisplay();
            int screenHeight = 0;
    
            if (Build.VERSION.SDK_INT >= 17) {
                Point size = new Point();
                display.getRealSize(size);
                screenHeight = size.y;
            } else if (Build.VERSION.SDK_INT >= 14) {
                try {
                    screenHeight = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
                } catch (Exception e) {
                    DisplayMetrics dm = new DisplayMetrics();
                    display.getMetrics(dm);
                    screenHeight = dm.heightPixels;
                }
            }
            return screenHeight;
        }
    

    I/tag: widthPixels: 1080
    I/tag: heightPixels: 1920
    I/tag: generalizedDpi: 480.0
    I/tag: 100dp == 300px

    呃。。。说好的400呢,怎么变成480.0了???
    网上找到一个我比较能接受的说法就是:为简便起见,Android 将所有屏幕密度分组为以下六种通用密度:

    • ldpi ~120dpi
    • mdpi ~160dpi
    • hdpi ~240dpi
    • xhdpi ~320dpi
    • xxhdpi ~480dpi
    • xxxhdpi ~640dpi
      每种通用密度都涵盖一个实际密度范围(图中下半部分)。

    于是,上面计算出来的400由于落在了xxhdpi的范围内,所以其对应的通用密度就是480。也就是说上面打印出来的其实是手机屏幕对应的通用密度。
    而在Android系统中使用(如进行dp与px的转换)的就是这些通用密度。我个人更喜欢称之为“系统dpi”,对应的上面根据实际屏幕参数计算的就叫“物理dpi”。

    顺带提一下,这个系统dpi其实是保持在Android系统的一个配置文件(/system/build.prop)里面,这个文件其中有一行:ro.sf.lcd_density=480,这个就是系统dpi。也就是说我们其实可以修改这个配置(需要root权限,修改后重启生效)为物理dpi,但其实没有这个必要就是了。

    如何适配屏幕

    回到最初的问题:当面对着所有的标注都是以px为单位的设计图时,应该怎样将其转换为dp,使得在(几乎)所有的屏幕上都能显示出相同的效果?
    根据前面几个小节,我目前能想到的答案就是:

    • 在xml布局文件中,就用设计图上的标注值跟设计图的系统dpi进行转换。例如:设计图是以iPhone 6s(1334 * 750 4.7'' 326ppi)来做的,那它的系统dpi应该就是320,则设计图上的标注转换成dp就是除以2。即设计图上标的是10px,转换成dp就是5dp。
    • 在java文件中,就使用以下方法去转换
    /**
         * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
         */
        public static int px2dip(Context context, float pxValue) {
            final float scale = context.getResources().getDisplayMetrics().density;
            return (int) (pxValue / scale + 0.5f);
        }
    

    至于其它单位(dp、sp等)转换成px则可以使用系统的TypedValue.applyDimension方法。

    但就算按照这种方法来做适配,仍然达不到理想的效果。例如,设计图上有一个ImageView的高度占了屏幕高度的一半即1334 /2 = 667(px),转换成dp就是667 / 2 = 333.5(dp)。在1080 * 1920,系统dpi为3的屏幕上所占的宽度就是333.5 * 3 + 0.5 = 1001(px)。而这个屏幕的一半应该是1920 / 2 = 960(px),误差为1001 - 960 = 41(px)。

    百分比布局

    使用百分比布局可以避免以上问题,关于百分比布局的使用可以参考这篇文章
    但是我在使用百分比布局的过程中也碰到了一些问题

    • 目前百分比布局只有PercentRelativeLayout、PercentFrameLayout,即只支持RelativeLayout与FrameLayout,没有PercentLinearLayout(当然LinearLayout有类似的layout_weight属性)。
    • 百分比布局的属性只支持宽高和margin(layout_widthPercent, layout_heightPercent, layout_marginPercent及其它margin属性),也就是说,不能用来设置字体的大小和其它大小。
    • 百分比布局的属性是相对于父布局而言的,在复杂、嵌套层次比较多的界面,计算百分比很麻烦,一旦设计图有修改就更麻烦了。
    • 一些特殊的场景很难用百分比布局去实现。

    一体、どうすればいいの?

    相关文章

      网友评论

          本文标题:关于Android的屏幕适配

          本文链接:https://www.haomeiwen.com/subject/theapttx.html