面试问你屏幕适配,那么你要知道为什么Android要做屏幕适配,因为Android是开源的, 各大厂商不仅可以对软件定制,还可以对硬件定制,这样就造成市场上不同分辨率的手机超多,现在估计得有几万或者几十万种,这就导致android设备的碎片化很严重。所以还是做ios很辛福啊,下面对一些概念弄清楚

屏幕尺寸:指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米

屏幕分辨率:是指横纵上的像素点 单位是px   1px = 1个像素点  一般是以纵向像素*横向像素  比如1920*1080   一个屏幕分辨率越高 显示效果就越好

 

屏幕像素密度:是指每英寸上的像素点数  单位是dpi 是dot per inch 的缩写,屏幕像素密度与屏幕尺寸以及屏幕分辨率有关

以Google的Nexus5为例,它的分辨率是1920*1080  它的屏幕尺寸是4.95inch  屏幕像素密度是445 这是怎么计算出来的呢?

1920*1920+1080*1080这值是4852800 然后开根号再除以4.95就得到是445.03175153177745

 

像素:构成图像的最小单位   美工或者设计师使用

dip:density independent pixels   是指密度 与像素无关以160dpi为基准,1dip = 1px  和dp一样

加入有二个设备 一个480*320 密度是160dpi.   另外一台是800*480像素密度是240dpi

比如你要在这二个屏幕上要TextView的宽度充满横屏除了使用match_parent还可以使用如下:

我们知道480*320 它的宽度是320px,它是以160dpi为基准的,1px = 1dip  那么它的宽度就是320px就可以 但是在800*480也就是说它的宽度是480px,该如何计算呢?这个也很简单,240/160=1/x;  求这x是多少1.5 相当于1dp = 1.5px  那么它的宽度就是320*1.5 其实这就是我们做屏幕适配使用到的核心技术,想要适配所有手机都是这么适配的。

 

我们在创建Android项目的时候 系统会帮助我们生成

drawable_mdpi

drawable_hdpi

drawable_xdpi

drawable_xxdpi

drawable_xxxdpi

对应的密度如下:

android 面试题 谈谈屏幕适配_第1张图片

上面是讲了基本的概念, 下面谈谈如何去适配?

第一种方案:限定符适配

分辨率限定符  drawable-hdpi  drawable-xdpi   drawable-xxdpi  

尺寸限定符layout-small  layout-large

最小宽度限定符:values-sw360dp values-sw384dp

屏幕方向限定符:layout_port layout-land

这种方案几乎不用,除非在一些很小公司 做出来的app没啥人用, 大点的额公司肯定不用这套方案,比如我一张图片要放在不同的分辨率下  不但给美工同事添加了工作量,app打包后体积一定会增大,维护起来很麻烦。

 

第二种方案:自定义像素适配

这种适配目前是最好的,几乎能适配市面上所有的适配  当初在上面公司 交给test in 一个三方的测试公司, 测试了600多设备  都没出现问题,所以这种很靠谱

实现方案:以美工的设计尺寸为原始尺寸,根据不同设备的密度  计算出宽和高  

代码如下:

public class UIAdapter {    private static volatile  UIAdapter instance = null;    //设计师的参考尺寸    private static final float defaultWidth = 1080;    private static final float defaultHeight = 1920;    //屏幕的真实尺寸    private int screenWidth;    private int screenHeight;    private UIAdapter(){    }    public static UIAdapter getInstance(){        if(null==instance){            synchronized (UIAdapter.class){                if(null==instance){                    instance = new UIAdapter();                }            }        }        return instance;    }    public void init(Context context) {        if(null==context){            return;        }        WindowManager wm = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);        DisplayMetrics displayMetrics = new DisplayMetrics();        wm.getDefaultDisplay().getMetrics(displayMetrics);        if(displayMetrics.widthPixels>displayMetrics.heightPixels){//横屏            screenWidth = displayMetrics.heightPixels;            screenHeight = displayMetrics.widthPixels;        }else{            screenWidth = displayMetrics.widthPixels;            screenHeight = displayMetrics.heightPixels-getStatusBarHeight(context);        }    }    /**     * 获取状态栏高度     * @param context     * @return     */    public static int getStatusBarHeight(Context context) {        Resources resources = context.getResources();        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");        int height = resources.getDimensionPixelSize(resourceId);        return height;    }    public  float scaleX(){        return screenWidth/defaultWidth;    }    public  float scaleY(){        return screenHeight/defaultHeight;    }    public void scaleView(View v, int w, int h, int l, int t, int r, int b) {        if(v==null){            return;        }        w = (int) (w*scaleX());        h = (int) (h*scaleY());        l = (int) (l*scaleX());        t = (int) (t*scaleY());        r = (int) (r*scaleX());        b = (int) (b*scaleY());        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) v.getLayoutParams();        if (params != null) {            params.width = w;            params.height = h;            params.setMargins(l, t, r, b);        }    }}

记得在Application初始化下:

public class MyApp extends Application {    @Override    public void onCreate() {        super.onCreate();        UIAdapter.getInstance().init(this);    }}

使用:

 UIAdapter.getInstance().scaleView(textview,540,200,0,0,0,0);

如果想显示屏幕的1/3的话就是360了宽度,是根据设计师给出来的宽度进行设置

第三种方案: 百分比适配

这是Google 提出来的一个解决适配方案,想要使用必须添加依赖:

 implementation 'com.android.support:percent:28.0.0'

主要就二个类:

PercentRelativeLayoutPercentFrameLayout

主要属性如下:

app:layout_heightPercent:用百分比表示高度app:layout_widthPercent:用百分比表示宽度app:layout_marginPercent:用百分比表示View之间的间隔app:layout_marginLeftPercent:用百分比表示左边间隔app:layout_marginRight:用百分比表示右边间隔app:layout_marginTopPercent:用百分比表示顶部间隔app:layout_marginBottomPercent:用百分比表示底部间隔app:layout_marginStartPercent:用百分比表示距离第一个View之间的距离app:layout_marginEndPercent:用百分比表示距离最后一个View之间的距离app:layout_aspectRatio:用百分比表示View的宽高比

简单的布局看看:

<?xml version="1.0" encoding="utf-8"?>    

其实真实的项目中都没用过,那么它的实现原理是什么样的,因为现在面试不问你怎么使用,怎么使用它时初级工程师干的活,做了3到5年的人怎么去跟哪些刚毕业或者从事2年的比,那么这个时候比的就是内功了,怎么体现你比那些人牛逼呢?看PercentRelativeLayout的源码大概知道它怎么弄的,我们根据它的源代码返照写个,我们在分析view的加载流程中你的xml布局怎么生成对应的类文件,在这就不分析view的加载流程了,我们在Activity中写的setContentView()是调用了PhoneWindow中setContentView():

@Override    public void setContentView(int layoutResID) {        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window        // decor, when theme attributes and the like are crystalized. Do not check the feature        // before this happens.        if (mContentParent == null) {            installDecor();        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {            mContentParent.removeAllViews();        }        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,                    getContext());            transitionTo(newScene);        } else {            mLayoutInflater.inflate(layoutResID, mContentParent);        }        mContentParent.requestApplyInsets();        final Callback cb = getCallback();        if (cb != null && !isDestroyed()) {            cb.onContentChanged();        }        mContentParentExplicitlySet = true;    }

layoutResId就是我们的xml布局,看这段代码:

 mLayoutInflater.inflate(layoutResID, mContentParent);

最终会调用LayoutInflater中的

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {

方法里面有一段很关键的代码:

final View temp = createViewFromTag(root, name, inflaterContext, attrs);                    ViewGroup.LayoutParams params = null;                    if (root != null) {                        if (DEBUG) {                            System.out.println("Creating params from root: " +                                    root);                        }                        // Create layout params that match root, if supplied                        params = root.generateLayoutParams(attrs);                        if (!attachToRoot) {                            // Set the layout params for temp if we are not                            // attaching. (If we are, we use addView, below)                            temp.setLayoutParams(params);                        }                    }

root变量可以看作是你布局中的根view

params = root.generateLayoutParams(attrs);

这个是获取ViewGroup.LayoutParams,如果你根view是RelativeLayout,那么LayoutParams类是干吗用的呢?进入到RelativeLayout中看看LayoutParams类

 public static class LayoutParams extends ViewGroup.MarginLayoutParams {        @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = {            @ViewDebug.IntToString(from = ABOVE,               to = "above"),            @ViewDebug.IntToString(from = ALIGN_BASELINE,      to = "alignBaseline"),            @ViewDebug.IntToString(from = ALIGN_BOTTOM,        to = "alignBottom"),            @ViewDebug.IntToString(from = ALIGN_LEFT,          to = "alignLeft"),            @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"),            @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT,   to = "alignParentLeft"),            @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT,  to = "alignParentRight"),            @ViewDebug.IntToString(from = ALIGN_PARENT_TOP,    to = "alignParentTop"),            @ViewDebug.IntToString(from = ALIGN_RIGHT,         to = "alignRight"),            @ViewDebug.IntToString(from = ALIGN_TOP,           to = "alignTop"),            @ViewDebug.IntToString(from = BELOW,               to = "below"),            @ViewDebug.IntToString(from = CENTER_HORIZONTAL,   to = "centerHorizontal"),            @ViewDebug.IntToString(from = CENTER_IN_PARENT,    to = "center"),            @ViewDebug.IntToString(from = CENTER_VERTICAL,     to = "centerVertical"),            @ViewDebug.IntToString(from = LEFT_OF,             to = "leftOf"),            @ViewDebug.IntToString(from = RIGHT_OF,            to = "rightOf"),            @ViewDebug.IntToString(from = ALIGN_START,         to = "alignStart"),            @ViewDebug.IntToString(from = ALIGN_END,           to = "alignEnd"),            @ViewDebug.IntToString(from = ALIGN_PARENT_START,  to = "alignParentStart"),            @ViewDebug.IntToString(from = ALIGN_PARENT_END,    to = "alignParentEnd"),            @ViewDebug.IntToString(from = START_OF,            to = "startOf"),            @ViewDebug.IntToString(from = END_OF,              to = "endOf")

它的构造函数:

public LayoutParams(Context c, AttributeSet attrs) {            super(c, attrs);            TypedArray a = c.obtainStyledAttributes(attrs,                    com.android.internal.R.styleable.RelativeLayout_Layout);            final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;            mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||                    !c.getApplicationInfo().hasRtlSupport());            final int[] rules = mRules;            //noinspection MismatchedReadAndWriteOfArray            final int[] initialRules = mInitialRules;            final int N = a.getIndexCount();            for (int i = 0; i < N; i++) {                int attr = a.getIndex(i);                switch (attr) {                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:                        alignWithParent = a.getBoolean(attr, false);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:                        rules[LEFT_OF] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:                        rules[RIGHT_OF] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:                        rules[ABOVE] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:                        rules[BELOW] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:                        rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:                        rules[ALIGN_LEFT] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop:                        rules[ALIGN_TOP] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight:                        rules[ALIGN_RIGHT] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom:                        rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:                        rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop:                        rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight:                        rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom:                        rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:                        rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal:                        rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:                        rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;                       break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf:                        rules[START_OF] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf:                        rules[END_OF] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart:                        rules[ALIGN_START] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd:                        rules[ALIGN_END] = a.getResourceId(attr, 0);                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart:                        rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0;                        break;                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd:                        rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0;                        break;                }            }

你会发现你在xml中写的这些属性:

 android:id="@+id/textview"        android:background="#f0f000"        app:layout_heightPercent="50%"        app:layout_widthPercent="50%"        android:text="Hello World!"        android:gravity="center"

都是通过LayoutParam加载进去的,那么我们就根据这个自己实现:首先定义一些属性,在values创建一个文件 attr 

<?xml version="1.0" encoding="utf-8"?>                                                        

下面是实现自定义RelativeLayout

public class PercentLayout extends RelativeLayout {    public PercentLayout(Context context) {        super(context);    }    public PercentLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    public PercentLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //获取父容器的尺寸        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int count = getChildCount();        for (int i = 0; i < count; i++) {            View child = getChildAt(i);            ViewGroup.LayoutParams params = child.getLayoutParams();            if (checkLayoutParams(params)){                LayoutParams lp = (LayoutParams)params;                 float widthPercent = lp.widthPercent;                 float heightPercent = lp.heightPercent;                 float marginLeftPercent = lp.marginLeftPercent;                 float marginRightPercent= lp.marginRightPercent;                 float marginTopPercent= lp.marginTopPercent;                 float marginBottomPercent = lp.marginBottomPercent;                 if (widthPercent > 0){                     params.width = (int) (widthSize * widthPercent);                 }                if (heightPercent > 0){                    params.height = (int) (heightSize * heightPercent);                }                if (marginLeftPercent > 0){                    ((LayoutParams) params).leftMargin = (int) (widthSize * marginLeftPercent);                }                if (marginRightPercent > 0){                    ((LayoutParams) params).rightMargin = (int) (widthSize * marginRightPercent);                }                if (marginTopPercent > 0){                    ((LayoutParams) params).topMargin = (int) (heightSize * marginTopPercent);                }                if (marginBottomPercent > 0){                    ((LayoutParams) params).bottomMargin = (int) (heightSize * marginBottomPercent);                }            }        }        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }    @Override    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {        return p instanceof LayoutParams;    }    public LayoutParams generateLayoutParams(AttributeSet attrs){        return new LayoutParams(getContext(), attrs);    }    public static class LayoutParams extends RelativeLayout.LayoutParams{        private float widthPercent;        private float heightPercent;        private float marginLeftPercent;        private float marginRightPercent;        private float marginTopPercent;        private float marginBottomPercent;        public LayoutParams(Context c, AttributeSet attrs) {            super(c, attrs);            //解析自定义属性            TypedArray a = c.obtainStyledAttributes(attrs,R.styleable.PercentLayout);            widthPercent = a.getFloat(R.styleable.PercentLayout_widthPercent, 0);            heightPercent = a.getFloat(R.styleable.PercentLayout_heightPercent, 0);            marginLeftPercent = a.getFloat(R.styleable.PercentLayout_marginLeftPercent, 0);            marginRightPercent = a.getFloat(R.styleable.PercentLayout_marginRightPercent, 0);            marginTopPercent = a.getFloat(R.styleable.PercentLayout_marginTopPercent, 0);            marginBottomPercent = a.getFloat(R.styleable.PercentLayout_marginBottomPercent, 0);            a.recycle();        }    }}

上面其实就是百分比的实现原理了.这种实现方案缺点就是如果你使用了三方库的话就没办法了,所以不适应真实的项目中,而且就提供了

  • PercentRelativeLayout
  • PercentFrameLayout

第四种方案:修改density方案来实现屏幕适配,要修改三个值 分别是

density是指屏幕的密度 可以理解为Android系统内部针对某个尺寸它的分辨率 它的缩放比例,  这个缩放比例是指屏幕上每一寸有160个像素点,比如某个屏幕上达到320px,那么它的density就是2了,

scaleDensity:是指字体的缩放比例

densityDpi:指屏幕上每一英寸上像素点有多少个比如160 或者320

为什么能通过这三个值能达到修改从而适配呢?因为在Android不管你在xml设置了什么 最终都是转换成像素(px)显示,在Android系统类TypedValue类中:

 public static float applyDimension(int unit, float value,                                       DisplayMetrics metrics)    {        switch (unit) {        case COMPLEX_UNIT_PX:            return value;        case COMPLEX_UNIT_DIP:            return value * metrics.density;        case COMPLEX_UNIT_SP:            return value * metrics.scaledDensity;        case COMPLEX_UNIT_PT:            return value * metrics.xdpi * (1.0f/72);        case COMPLEX_UNIT_IN:            return value * metrics.xdpi;        case COMPLEX_UNIT_MM:            return value * metrics.xdpi * (1.0f/25.4f);        }        return 0;    }

这个就是把其他单位转换成px,

density不同的设备它的值不一样,而且相同的分辨率下density也可能不一样,所以我们要对它进行调整处理,让density随着分辨率的变化而变化,但是这个也要设计师给出参考密度

代码如下:

public class Density {    private static final float  WIDTH = 320;//参考设备的宽,单位是dp 320 / 2 = 160    private static float appDensity;//表示屏幕密度    private static float appScaleDensity; //字体缩放比例,默认appDensity    public static void setDensity(final Activity activity){        //获取当前app的屏幕显示信息        DisplayMetrics displayMetrics = activity.getApplication().getResources().getDisplayMetrics();        if (appDensity == 0){            //初始化赋值操作            appDensity = displayMetrics.density;            appScaleDensity = displayMetrics.scaledDensity;            //添加字体变化监听回调            activity.getApplication().registerComponentCallbacks(new ComponentCallbacks() {                @Override                public void onConfigurationChanged(Configuration newConfig) {                    //字体发生更改,重新对scaleDensity进行赋值                    if (newConfig != null && newConfig.fontScale > 0){                        appScaleDensity = activity.getApplication().getResources().getDisplayMetrics().scaledDensity;                    }                }                @Override                public void onLowMemory() {                }            });        }        //计算目标值density, scaleDensity, densityDpi        float targetDensity = displayMetrics.widthPixels / WIDTH; // 1080 / 360 = 3.0        float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);        int targetDensityDpi = (int) (targetDensity * 160);        //替换Activity的density, scaleDensity, densityDpi        DisplayMetrics dm = activity.getResources().getDisplayMetrics();        dm.density = targetDensity;        dm.scaledDensity = targetScaleDensity;        dm.densityDpi = targetDensityDpi;    }}

记住在activity的setContentView()前面调用,上面四种方案就是屏幕适配的几种方案了,我是选择了第二种项目中

 

 

 

 

更多相关文章

  1. [Android] 一种粗暴快速的 Android 全屏幕适配方案
  2. android自定义adapter 滑动屏幕时 进度条显示混乱
  3. Android屏幕横竖屏切换和生命周期管理的详细总结
  4. Android屏幕锁定实例源码详解教程一
  5. android 固定横屏幕竖屏
  6. android 全屏幕显示以及竖屏显示
  7. android调节屏幕亮度
  8. android根据屏幕高度改变item占ListView高度

随机推荐

  1. android adb shel l命令使用 解决 Read-o
  2. Android(安卓)Studio与 Android(安卓)SDK
  3. Android用http协议上传文件
  4. 第一篇 GridView控件
  5. Android(安卓)CollapsingToolbarLayout:将
  6. Android资源管理框架-------之总述(一)
  7. android设置wallpaper
  8. 读取SIM卡信息
  9. First Android(安卓)application
  10. 自定义android 机器人