总结Android屏幕适配

2016-07-10  本文已影响528人  李俊的博客

基础知识

屏幕尺寸、屏幕分辨率、屏幕像素密度

1、屏幕尺寸是指屏幕对角线的长度。单位是英寸,1英寸=2.54厘米。
2、屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1像素点,一般是纵向像素横向像素,如1280×720。
3、屏幕像素密度是指每英寸上的像素点数,单位是dpi(dot per inch),像素密度和屏幕尺寸和屏幕分辨率有关。


dip、dp、sp、dpi、px、in、pt

1、dip或dp(device independent pixels,设备独立像素):以160dpi为基准,1dp=1px。
2、sp(scaled pixels):可以根据文字大小首选项自动进行缩放。
3、dpi(dots per inch,密度):屏幕像素密度的单位,屏幕每英寸所包含的像素数。
4、px(pixels,像素):物理上的绝对单位。
5、in(inch,英寸):一英寸=2.54cm。
6、pt(磅):一磅=1/72英寸。

mdpi、hdpi、xdpi、xxdpi、xxxdpi
密度分类 密度值范围 代表分辨率 图标尺寸 图片比例
mdpi 120~160dpi 320x480px 48x48px 1
hdpi 160~240dpi 480x800px 72x72px 1.5
xhdpi 240~320dpi 720x1280px 96x96px 2
xxhdpi 320~480dpi 1080x1920px 144x144px 3
xxxhdpi 480~640dpi 1440x2560px 192x192px 4
实战

获取屏幕的数据

// 获取DisplayMetrics实例
DisplayMetrics dm = context.getResources().getDisplayMetrics();

// 屏幕高的像素个数
int heightPixels = dm.heightPixels;
// 屏幕宽的像素个数
int widthPixels = dm.widthPixels;

// 屏幕密度:每英寸所含的像素的个数
int densityDpi = dm.densityDpi;
// 屏幕密度:densityDpi/160
float density = dm.density;

dp与px相互转换

/**
 * 转换工具类
 */
public class ConvertUtil {

    /**
     * dp转换成px
     *
     * @param context 上下文
     * @param value   多少dp
     * @return 返回转换后等于多少px
     */
    public static int dp2px(Context context, float value) {

        // 获取屏幕的密度:density = densityDpi/160
        final float scale = context.getResources().getDisplayMetrics().density;
        // dp转换成px,value*density
        // + 0.5f是为了四舍五入转换成int类型
        return (int) (value * scale + 0.5f);
    }

    public static int px2dp(Context context, float value) {
        
        final float scale = context.getResources().getDisplayMetrics().density;
        // px转换成dp,value/density
        return (int) (value / scale + 0.5f);
    }
}

注意:因为分辨率不一样,所以不能用px;因为屏幕宽度不一样,所以要小心的用dp。

解决方案

一、支持各种屏幕尺寸

1、使用wrap_content、math_parent、weight

wrap_content:根据控件的内容设置控件的尺寸
math_parent:根据父控件的尺寸大小设置控件的尺寸
weight:权重,在线性布局中可以使用weight属性设置控件所占的比例

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
android:layout_width="match_parent"    
android:layout_height="match_parent"    
android:orientation="horizontal">    
<TextView        
      android:layout_width="80dp"        
      android:layout_height="80dp"        
      android:layout_weight="0"        
      android:background="#028330"/>    
<TextView        
      android:layout_width="wrap_content"        
      android:layout_height="80dp"        
      android:text="new reader"        
      android:textSize="22sp"        
      android:layout_weight="1"/>    
<TextView        
      android:layout_width="160dp"        
      android:layout_height="80dp"       
      android:text="Politics"        
      android:textSize="18sp"        
      android:layout_weight="0"        
      android:background="#028330"/>
</LinearLayout>

实现下图所显示的效果:当屏幕尺寸改变时,new reader控件两边的控件大小不变,new reader控件会占完剩余的空间。


android:layout_weight
公式:所占宽度=原来宽度+剩余空间所占百分比的宽度

2、使用相对布局,禁用绝对布局

简单的布局一般都使用线性布局,而略微复杂点的布局,我们使用相对布局,大多数时候,我们都是使用这两种布局的嵌套。我们使用相对布局的原因是,相对布局能在各种尺寸的屏幕上保持控件间的相对位置。

3、使用限定符
一、使用尺寸限定符

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

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

    <fragment
        android:id="@+id/news_title_fragment"
        android:name="com.jun.androidexample.qualifier.NewsTitleFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>

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

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

    <fragment
        android:id="@+id/news_title_fragment"
        android:name="com.jun.androidexample.qualifier.NewsTitleFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <FrameLayout
        android:id="@+id/news_content_layout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3">

        <fragment
            android:id="@+id/news_content_fragment"
            android:name="com.jun.androidexample.qualifier.NewsContentFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </FrameLayout>
</LinearLayout>

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

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

二、使用最小宽度限定符

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

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

    <fragment
        android:id="@+id/news_title_fragment"
        android:name="com.jun.androidexample.qualifier.NewsTitleFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>

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

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

    <fragment
        android:id="@+id/news_title_fragment"
        android:name="com.jun.androidexample.qualifier.NewsTitleFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <FrameLayout
        android:id="@+id/news_content_layout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3">

        <fragment
            android:id="@+id/news_content_fragment"
            android:name="com.jun.androidexample.qualifier.NewsContentFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </FrameLayout>
</LinearLayout>

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

三、使用布局别名

单面板:res/layout/activity_qualifier.xml
双面板:res/layout-large/activity_qualifier.xml
双面板:res/layout-sw600dp/activity_qualifier.xml

因为双面板的布局一样,所以为了方便管理使用以下2个文件代替上面的3个文件。
单面板:res/layout/activity_qualifier_onepane.xml
双面板:res/layout/activity_qualifier_twopanes.xml

然后创建如下3个文件
默认布局,单面板:res/values/layout.xml

<resources>
    <item name="activity_qualifier" type="layout">@layout/activity_qualifier_onepane</item>
</resources>

Android3.2之前的大屏布局,双面板:res/values-large/layout.xml

<resources>
    <item name="activity_qualifier" type="layout">@layout/activity_qualifier_twopanes</item>
</resources>

Android3.2之后的大于600dp屏布局,双面板:res/values-sw600dp/layout.xml

<resources>
    <item name="activity_qualifier" type="layout">@layout/activity_qualifier_twopanes</item>
</resources>

这样无论是哪一种名字都叫activity_qualifier。

记得将之前创建的res/layout-large和res/layout-sw600dp
双面板:res/layout-large/activity_qualifier.xml
双面板:res/layout-sw600dp/activity_qualifier.xml,Small Width最小宽度
都删掉,因为已经不需要了。

四、使用屏幕方向限定符

res/values-port/layout.xml,纵向,单面板

<resources>
    <item name="activity_qualifier" type="layout">@layout/activity_qualifier_onepane</item>
</resources>

res/values-land/layout.xml,横向,双面板

<resources>
    <item name="activity_qualifier" type="layout">@layout/activity_qualifier_twopanes</item>
</resources>
4、使用自动拉伸位图

.9.png格式图片

二、支持各种屏幕密度

1、统一划分

统一划分解决分辨率不一致,宽高不一致的问题。把需要适配的手机屏幕的宽度像素均分为320份,高度像素均分为480份。使用我们写好的程序自动生成资源values-×文件夹,里面包含lay_x.xml和lay_y.xml,分别对应宽度和高度的像素。

在Eclipse中运行如下代码自动生成需要的文件

/**
 * 将所有不同分辨率的屏幕都分成480x320
 */
public class MakeXml {
    
    // 生成文件存放的地址是C:\layoutRoot
    private final static String rootPath = "C:\\layoutRoot\\values-{0}x{1}\\";

    // 分成480x320
    private final static float dw = 320f;
    private final static float dh = 480f;

    private final static String WTemplate = "<dimen name=\"x{0}\">{1}px</dimen>\n";
    private final static String HTemplate = "<dimen name=\"y{0}\">{1}px</dimen>\n";

    public static void main(String[] args) {
        setResolution();
    }
    
    // 设置需要适配的不同屏幕的分辨率
    public static void setResolution(){
        makeString(320, 480);
        makeString(480, 800);
        makeString(480, 854);
        makeString(540, 960);
        makeString(600, 1024);
        makeString(720, 1184);
        makeString(720, 1196);
        makeString(720, 1280);
        makeString(768, 1024);
        makeString(800, 1280);
        makeString(1080, 1812);
        makeString(1080, 1920);
        makeString(1440, 2560);
    }
    
    public static void makeString(int w, int h) {

        StringBuffer sb = new StringBuffer();
        sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sb.append("<resources>");
        float cellw = w / dw;
        for (int i = 1; i < 320; i++) {
            sb.append(WTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellw * i) + ""));
        }
        sb.append(WTemplate.replace("{0}", "320").replace("{1}", w + ""));
        sb.append("</resources>");

        StringBuffer sb2 = new StringBuffer();
        sb2.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sb2.append("<resources>");
        float cellh = h / dh;
        for (int i = 1; i < 480; i++) {
            sb2.append(HTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellh * i) + ""));
        }
        sb2.append(HTemplate.replace("{0}", "480").replace("{1}", h + ""));
        sb2.append("</resources>");

        String path = rootPath.replace("{0}", h + "").replace("{1}", w + "");
        File rootFile = new File(path);
        if (!rootFile.exists()) {
            rootFile.mkdirs();
        }
        File layxFile = new File(path + "lay_x.xml");
        File layyFile = new File(path + "lay_y.xml");
        try {
            PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
            pw.print(sb.toString());
            pw.close();
            pw = new PrintWriter(new FileOutputStream(layyFile));
            pw.print(sb2.toString());
            pw.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }

    public static float change(float a) {
        int temp = (int) (a * 100);
        return temp / 100f;
    }
}

将C:\layoutRoot目录下的文件拷贝到Android Studio中app/src/main/res/目录下

使用示例:

<Button
    android:background="#abd123"
    android:layout_width="@dimen/x160"
    android:layout_height="@dimen/y240"/>

宽和高都是屏幕的一半

2、提供备用位图

针对不同分辨率的屏幕提供不同分辨率的位图
res/drawable-mdpi
res/drawable-hdpi
res/drawable-xhdpi
res/drawable-xxhdpi
res/drawable-xxxhdpi

如果只提供一张位图就应该放置在应对分辨率的文件夹中

三、实施自适应用户界面流程

1、确定当前布局
boolean isTwoPane;
// 能找到news_content_layout就是双页否则就是单页
View view = getActivity().findViewById(R.id.news_content_layout);
if (view != null && view.getVisibility() == View.VISIBLE) {
    isTwoPane = true;
} else {
    isTwoPane = false;
}
2、根据当前布局做出响应

在单面板模式下,用户点击了新闻标题,我们要打开一个新的Activity来显示新闻详细信息;在双面板模式下,用户点击了新闻标题,我们要在右边面板上显示详细信息。


Percent Support Library(百分比布局库)

添加依赖

compile 'com.android.support:percent:23.3.0'

使用示例
android-percent-support-lib-sample

上一篇下一篇

猜你喜欢

热点阅读