总结Android屏幕适配

基础知识

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容