Android四种适配方式

适配方式

方式一、生成对应px文件,百分比布局
方式二、动态改变px比例,百分比布局
方式三、生成对应dp文件,百分比布局
方式四、改变DisplayMetrics属性值,今日头条方案

获取设备信息

private void getMessage() {
        // 获取屏幕密度
        DisplayMetrics dm = new DisplayMetrics();
        dm = getResources().getDisplayMetrics();

        float density  = dm.density;        // 屏幕密度(像素比例:0.75/1.0/1.5/2.0)
        int densityDPI = dm.densityDpi;        // 屏幕密度(每寸像素:120/160/240/320)
        float xdpi = dm.xdpi;
        float ydpi = dm.ydpi;

        Log.e("  DisplayMetrics", "xdpi=" + xdpi + "; ydpi=" + ydpi);
        Log.e( "  DisplayMetrics", "density=" + density + "; densityDPI=" + densityDPI);
        Log.e( "  DisplayMetrics", "screenWidth=" + dm.widthPixels + "; screenHeight=" + dm.heightPixels);
    }

生对应px文件,百分比布局

该适配方案适配主流分辨率,对于特定并且没有适配的需要一个默认文件设置大小。
对于已经适配分辨率的手机支持不错,使用没有什么需要特别注意的。
如果UI设计采用的是px单位,建议使用这种适配。需要注意的是,该手机真实分辨率。

GenerateValueFiles.class生成px文件

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;

public class GenerateValueFiles {

    private int baseW;
    private int baseH;

    private String dirStr = "F:/res";


    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";

    /**
     * {0}-HEIGHT
     */
    private final static String VALUE_TEMPLATE = "values-{0}x{1}";

    private static final String SUPPORT_DIMESION = "1440,2392;320,480;480,800;480,854;540,960;600,1024;720,1184;720,1196;720,1280;768,1024;800,1280;1080,1812;1080,1920;1440,2560;";

    private String supportStr = SUPPORT_DIMESION;

    public GenerateValueFiles(int baseX, int baseY, String supportStr) {
        this.baseW = baseX;
        this.baseH = baseY;

        if (!this.supportStr.contains(baseX + "," + baseY)) {
            this.supportStr += baseX + "," + baseY + ";";
        }

        this.supportStr += validateInput(supportStr);

        System.out.println(supportStr);

        File dir = new File(dirStr);
        if (!dir.exists()) {
            dir.mkdir();

        }
        System.out.println(dir.getAbsoluteFile());

    }

    /**
     * @param supportStr
     *            w,h_...w,h;
     * @return
     */
    private String validateInput(String supportStr) {
        StringBuffer sb = new StringBuffer();
        String[] vals = supportStr.split("_");
        int w = -1;
        int h = -1;
        String[] wh;
        for (String val : vals) {
            try {
                if (val == null || val.trim().length() == 0)
                    continue;

                wh = val.split(",");
                w = Integer.parseInt(wh[0]);
                h = Integer.parseInt(wh[1]);
            } catch (Exception e) {
                System.out.println("skip invalidate params : w,h = " + val);
                continue;
            }
            sb.append(w + "," + h + ";");
        }

        return sb.toString();
    }

    public void generate() {
        String[] vals = supportStr.split(";");
        for (String val : vals) {
            String[] wh = val.split(",");
            generateXmlFile(Integer.parseInt(wh[0]), Integer.parseInt(wh[1]));
        }

    }

    private void generateXmlFile(int w, int h) {

        StringBuffer sbForWidth = new StringBuffer();
        sbForWidth.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sbForWidth.append("<resources>");
        float cellw = w * 1.0f / baseW;

        System.out.println("width : " + w + "," + baseW + "," + cellw);
        for (int i = 1; i < baseW; i++) {
            sbForWidth.append(WTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellw * i) + ""));
        }
        sbForWidth.append(WTemplate.replace("{0}", baseW + "").replace("{1}",
                w + ""));
        sbForWidth.append("</resources>");

        StringBuffer sbForHeight = new StringBuffer();
        sbForHeight.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sbForHeight.append("<resources>");
        float cellh = h *1.0f/ baseH;
        System.out.println("height : "+ h + "," + baseH + "," + cellh);
        for (int i = 1; i < baseH; i++) {
            sbForHeight.append(HTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellh * i) + ""));
        }
        sbForHeight.append(HTemplate.replace("{0}", baseH + "").replace("{1}",
                h + ""));
        sbForHeight.append("</resources>");

        File fileDir = new File(dirStr + File.separator
                + VALUE_TEMPLATE.replace("{0}", h + "")//
                        .replace("{1}", w + ""));
        fileDir.mkdir();

        File layxFile = new File(fileDir.getAbsolutePath(), "lay_x.xml");
        File layyFile = new File(fileDir.getAbsolutePath(), "lay_y.xml");
        try {
            PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
            pw.print(sbForWidth.toString());
            pw.close();
            pw = new PrintWriter(new FileOutputStream(layyFile));
            pw.print(sbForHeight.toString());
            pw.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

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

    public static void main(String[] args) {
        int baseW = 1080;
        int baseH = 1920;
        String addition = "";
        try {
            if (args.length >= 3) {
                baseW = Integer.parseInt(args[0]);
                baseH = Integer.parseInt(args[1]);
                addition = args[2];
            } else if (args.length >= 2) {
                baseW = Integer.parseInt(args[0]);
                baseH = Integer.parseInt(args[1]);
            } else if (args.length >= 1) {
                addition = args[0];
            }
        } catch (NumberFormatException e) {

            System.err
                    .println("right input params : java -jar xxx.jar width height w,h_w,h_..._w,h;");
            e.printStackTrace();
            System.exit(-1);
        }

        new GenerateValueFiles(baseW, baseH, addition).generate();
    }
}

使用方式,导入res中

此处输入图片的描述

在布局使用
此处输入图片的描述

注意在生成文件是使用1920X1080为基准;所以x轴方向x540表示占屏幕一半。

动态改变px比例,百分比布局

参见原文注意事项

public class PxAutoUtils {

    public static int displayWidth;
    public static int displayHeight;

    private static int designWidth;
    private static int designHeight;

    private static double textPixelsRate;


    public static void setSize(Activity act, boolean hasStatusBar, int designWidth, int designHeight) {

        if (act == null || designWidth < 1 || designHeight < 1)
            return;

        Display display = act.getWindowManager().getDefaultDisplay();
        int width = display.getWidth();
        int height = display.getHeight();


        if (hasStatusBar) {
            height -= getStatusBarHeight(act);
        }

        PxAutoUtils.displayWidth = width;
        PxAutoUtils.displayHeight = height;

        PxAutoUtils.designWidth = designWidth;
        PxAutoUtils.designHeight = designHeight;

        double displayDiagonal = Math.sqrt(Math.pow(PxAutoUtils.displayWidth, 2) + Math.pow(PxAutoUtils.displayHeight, 2));
        double designDiagonal = Math.sqrt(Math.pow(PxAutoUtils.designWidth, 2) + Math.pow(PxAutoUtils.designHeight, 2));

        PxAutoUtils.textPixelsRate = displayDiagonal / designDiagonal;

    }


    public static int getStatusBarHeight(Activity context) {

        int result = 0;
        try {
            int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
            if (resourceId > 0) {
                result = context.getResources().getDimensionPixelSize(resourceId);
            }

        } catch (Resources.NotFoundException e) {
            e.printStackTrace();
        }

        return result;

    }


    public static void auto(Activity act) {
        if (act == null || displayWidth < 1 || displayHeight < 1)
            return;
        View view = act.getWindow().getDecorView();
        auto(view);
    }


    public static void auto(View view) {

        if (view == null || displayWidth < 1 || displayHeight < 1)
            return;

        PxAutoUtils.autoTextSize(view);
        PxAutoUtils.autoSize(view);
        PxAutoUtils.autoPadding(view);
        PxAutoUtils.autoMargin(view);

        if (view instanceof ViewGroup) {
            auto((ViewGroup) view);
        }
    }


    private static void auto(ViewGroup viewGroup) {

        int count = viewGroup.getChildCount();

        for (int i = 0; i < count; i++) {
            View child = viewGroup.getChildAt(i);

            if (child != null) {
                auto(child);
            }
        }

    }


    public static void autoMargin(View view) {

        if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams))
            return;


        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        if (lp == null)
            return;


        lp.leftMargin = getDisplayWidthValue(lp.leftMargin);
        lp.topMargin = getDisplayHeightValue(lp.topMargin);
        lp.rightMargin = getDisplayWidthValue(lp.rightMargin);
        lp.bottomMargin = getDisplayHeightValue(lp.bottomMargin);

    }


    public static void autoPadding(View view) {

        int l = view.getPaddingLeft();
        int t = view.getPaddingTop();
        int r = view.getPaddingRight();
        int b = view.getPaddingBottom();


        l = getDisplayWidthValue(l);
        t = getDisplayHeightValue(t);
        r = getDisplayWidthValue(r);
        b = getDisplayHeightValue(b);

        view.setPadding(l, t, r, b);
    }


    public static void autoSize(View view) {

        ViewGroup.LayoutParams lp = view.getLayoutParams();

        if (lp == null)
            return;

        boolean isSquare = false;
        if (lp.width == lp.height) {
            isSquare = true;
        }

        if (lp.width > 0) {
            lp.width = getDisplayWidthValue(lp.width);
        }

        if (lp.height > 0) {
            lp.height = getDisplayHeightValue(lp.height);
        }

        if (isSquare) {
            if (lp.width > lp.height) {
                lp.width = lp.height;
            } else {
                lp.height = lp.width;
            }
        }

    }


    public static void autoTextSize(View view) {

        if (view instanceof TextView) {

            double designPixels = ((TextView) view).getTextSize();
            double displayPixels = textPixelsRate * designPixels;

            ((TextView) view).setIncludeFontPadding(false);
            ((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, (float) displayPixels);

        }

    }


    public static int getDisplayWidthValue(int designWidthValue) {

        if (Math.abs(designWidthValue) < 2) {
            return designWidthValue;
        }

        return designWidthValue * displayWidth / designWidth;

    }


    public static int getDisplayHeightValue(int designHeightValue) {

        if (Math.abs(designHeightValue) < 2) {
            return designHeightValue;
        }
        return designHeightValue * displayHeight / designHeight;

    }

    public static float getDisplayTextSize(float designTextSize){
        return (float) (PxAutoUtils.textPixelsRate*designTextSize);
    }

    /**
     * 适配所有Activity
     * @param application
     * @param width
     * @param height
     */
    private static  Boolean KEY_ISFIRST = true;
    public static void setAuto(Application application, final int width, final int height){

        application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks(){
            @Override
            public void onActivityCreated(Activity activity, Bundle bundle) {

                if(KEY_ISFIRST){
                    KEY_ISFIRST=false;
                    setSize(activity,false,width,height);
                }
            }

            @Override
            public void onActivityStarted(Activity activity) {

                auto(activity);
            }

            @Override
            public void onActivityResumed(Activity activity) {

            }

            @Override
            public void onActivityPaused(Activity activity) {

            }

            @Override
            public void onActivityStopped(Activity activity) {

            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });

    }
}

在application注册即可适配activity页面

PxAutoUtils.setAuto(this,1080,1920);

对于加载自定义View,fragment,listview,recycleview时使用

PxAutoUtils.auto(view);

虽然使用简单,但是并不是非常建议使用,对于一切都是以px为单位;同样是以1080X1920为基准。假设某个页面或者布局被加载设置PxAutoUtils.auto(view)两次或者多次,就会改变正确数据;而对于某些特定系统页面也不能做到适配view.

生成对应dp文件,百分比布局

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;

public class TestDp {


    public static void main(String[] args) {

        String baseUrl="F://dpres//value";
        int baseDp=360;
        String[] defaultDPArr = new String[]{"240","267","320","360","384", "392", "400", "410", "411", "432","440","480", "533", "560","592", "600", "640", "662", "720", "768", "800", "811", "820", "960", "961", "1024", "1280", "1365"};

        for(int i=0;i<defaultDPArr.length;i++){

            Integer value=Integer.valueOf(defaultDPArr[i]);
            getFile(baseDp,value,baseUrl);

        }


    }

    private static void getFile(float baseDp,int Dp, String pathFile ) {

        float size=Dp/baseDp;

        StringBuilder sb=new StringBuilder();
        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>");
        sb.append("\n");


        for(float i=0;i<1;i=i+0.1f){

            float temp=i*size;
            String value=String.valueOf(temp);
            if(value.length()>=3){
                value=value.substring(0,3);
            }

            String value0=String.valueOf(i);
            if(i==0){
                value0="0";
            }
            if(value0.length()>=3){
                value0=value0.substring(0,3);
            }

            sb.append("<dimen name=\"").append("dp").append(value0).append("\">").append(value).append("dp").append("</dimen>").append("\n");                

        }


        for(int i=1;i<=baseDp;i=i+1){
            float temp=i*size;
            String value=String.valueOf(temp);
            sb.append("<dimen name=\"").append("dp").append(i).append("\">").append(value).append("dp").append("</dimen>").append("\n");                

        }


        for(int i=1;i<=50;i=i+1){
            float temp=i*size;
            String value=String.valueOf(temp);
            sb.append("<dimen name=\"").append("sp").append(i).append("\">").append(value).append("sp").append("</dimen>").append("\n");                

        }


        sb.append("</resources>\n");

        //System.out.println(sb.toString());
        String dir=pathFile+"//values-sw"+Dp+"dp";

        try {
            if(!new File(dir).exists()){                
                new File(dir).mkdirs();
            }

            String fileNme=dir+"//dimens.xml";
            System.out.println(fileNme);

            if(new File(fileNme).exists()){
                new File(fileNme).delete();
            }

            new File(fileNme).createNewFile();

            FileOutputStream outSTr = new FileOutputStream(new File(fileNme));
            BufferedOutputStream Buff = new BufferedOutputStream(outSTr);
            Buff.write(sb.toString().getBytes());
            Buff.flush();
            Buff.close();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

计算dp值

dp=宽度px/设备密度值(density);
比如720px density为2 dp为360;1080px density为3 dp为360;
dpi=160*density;

sw为small width,后面接的值表示,屏幕的最小宽度dp大于这个值的时候启用;会使用最接近文件适配。

使用

改变DisplayMetrics属性值,今日头条方案

public class DpAutoUtils {

    private static float appDensity;
    private static float appScaledDensity;
    private static DisplayMetrics appDisplayMetrics;
    /**
     * 用来参照的的width
     */
    private static float WIDTH;

    public static void setDensity(@NonNull final Application application, float width) {
        appDisplayMetrics = application.getResources().getDisplayMetrics();
        WIDTH = width;
        registerActivityLifecycleCallbacks(application);

        if (appDensity == 0) {
            //初始化的时候赋值
            appDensity = appDisplayMetrics.density;
            appScaledDensity = appDisplayMetrics.scaledDensity;

            //添加字体变化的监听
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(Configuration newConfig) {
                    //字体改变后,将appScaledDensity重新赋值
                    if (newConfig != null && newConfig.fontScale > 0) {
                        appScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }
                @Override
                public void onLowMemory() {
                }
            });
        }
    }


    private static void setDefault(Activity activity) {
        setAppOrientation(activity);
    }

    private static void setAppOrientation(@Nullable Activity activity) {

        float targetDensity = 0;
        try {
            targetDensity = appDisplayMetrics.widthPixels / WIDTH;
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }

        float targetScaledDensity = targetDensity * (appScaledDensity / appDensity);
        int targetDensityDpi = (int) (160 * targetDensity);

        /**
         *
         * 最后在这里将修改过后的值赋给系统参数
         *
         * 只修改Activity的density值
         */

        DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.scaledDensity = targetScaledDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }


    private static void registerActivityLifecycleCallbacks(Application application) {
        application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                setDefault(activity);
            }
            @Override
            public void onActivityStarted(Activity activity) {
            }
            @Override
            public void onActivityResumed(Activity activity) {
            }
            @Override
            public void onActivityPaused(Activity activity) {
            }
            @Override
            public void onActivityStopped(Activity activity) {
            }
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }

}

使用在application注册

DpAutoUtils.setDensity(this,360);

360根据设计图宽度改变;最简单直接一种适配,全局不用改变,该dp用dp,该sp用sp;但是不能使用px。

总结

综上所述;如果设计图是dp为单位,用方案四今日头条比较好,毕竟大厂有一定保障性,其次是方案三dp文件,使用这种适配也不少;如果设计图以px为单位,对适配范围不那么严谨的话使用方案一px文件比较好,适配主流屏幕不错;最次就是方案二动态px适配,虽然考虑到所有屏幕,但是程序app中可能出现异常界面情况,而且适配view不太友好,有很大侵入性,所以不太推荐。

其他基本适配就是使用xml中特有属性适配,比如权重,wrap_content,match_parent等

代码下载

Demo