一、屏幕适配概念
1.什么是屏幕尺寸、屏幕分辨率、屏幕像素密度?
屏幕尺寸:指屏幕的对角线的长度,单位:英寸,1英寸=2.54厘米;
屏幕分辨率:指横向和纵向上的像素点数,单位:px,1px=1个像素点,一般以纵向横向像素:19201080;
屏幕像素密度:指每英寸上的像素点数,单位是dpi,即dot per inch的缩写,屏幕像素密度与屏幕尺寸和屏幕分辨率有关。密度计算:例如sqrt(1080^2 + 1920^2)/ 屏幕尺寸。
2.什么是dp、dpi、sp、px?它们之间的关系?
px:构成图像的最小单位;
dpi,dip:Desity Independent pixels的缩写,即密度无关像素,以160dpi为基准;1dip=1px或1dp=1px;
sp:可以根据文字大小首选项进行缩放,google推荐使用偶数。
二、屏幕适配基础篇
2.1使用“wrap_content”和“match_parent”;
2.2使用相对布局控制屏幕;
2.3 多使用.9图
2.4 限定符
我们在做屏幕适配时,在屏幕尺寸相差不大的情况下,dp可以使不同分辨率的设备上展示效果相似,但是在屏幕尺寸相差比较大的情况下(平板),dp就失去了这种效果,所以需要以下的限定符来约束,采用多套布局,数值等方式来适配。
所谓的限定符就是android在进行资源加载的时候会按照屏幕的相关信息对文件夹对应的文字进行识别,而这些特殊名字就是我们的限定符。
限定符分类:
屏幕尺寸
small 小屏幕
normal 基准屏幕
large 大屏幕
xlarge 超大屏幕
屏幕密度
ldpi <=120dpi
mdpi <= 160dpi
hdpi <= 240dpi
xhdpi <= 320dpi
xxhdpi <= 480dpi
xxhdpi <= 640dpi(只用来存放icon)
nodpi 与屏幕密度无关的资源.系统不会针对屏幕密度对其中资源进行压缩或者拉伸
tvdpi 介于mdpi与hdpi之间,特定针对213dpi,专门为电视准备的,手机应用开发不需要关心这个密度值.
屏幕方向
land 横向
port 纵向
屏幕宽高比
long 比标准屏幕宽高比明显的高或者宽的这样屏幕
notlong 和标准屏幕配置一样的屏幕宽高比
2.4.1 使用尺寸限定符
当我们要在大屏幕上显示不同的布局,就要使用large限定符,例如,在宽的屏幕左边显示列表,右边显示列表的详细信息,在一般宽度的屏幕只显示列表,不显示列表项的详细信息,我们就可以使用large限定符。
res/layout/main.xml 单面板:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 列表 -->
<fragment android:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="match_parent" />
</LinearLayout>
res/layout-large/main.xml 双面板:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<!-- 列表 -->
<fragment android:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="400dp"
android:layout_marginRight="10dp"/>
<!-- 列表项的详细信息 -->
<fragment android:id="@+id/article"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.ArticleFragment"
android:layout_width="fill_parent" />
</LinearLayout>
如果这个程序运行在屏幕尺寸大于7inch的设备上,系统就会加载res/layout-large/main.xml,而不是res/layout/main.xml。
不过在android3.0之后,Google退出了最小宽度限定符:
2.4.2 使用最小宽度限定符
先用AndroidStudio安装ScreenMatch插件;
使用ScreenMatch插件会自动帮我们生成layout-sw600dp这种类似的xml文件
三、屏幕适配解决方案:
基础篇介绍之后,市场上最常用高级解决方案有两种:
3.1 给各个分辨率单独适配,res、dimens里面设置每个对应的px,再统一调用,由系统删选。这里会用到ScreenMatch插件,使用方式就不细说;
3.2 通过自定义布局来完成屏幕适配。
核心原理是根据一个参照分辨率进行布局,然后按各个机器上提取当前机器分辨率换算出系数后,再通过重新测量的方式来达到适配的效果。如果需要适配刘海屏,只需要加上沉浸式代码控制即可。
/**
* Created by barry on 2018/6/7.
*/
public class ScreenAdaptationRelaLayout extends RelativeLayout {
public ScreenAdaptationRelaLayout(Context context) {
super(context);
}
public ScreenAdaptationRelaLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScreenAdaptationRelaLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
static boolean isFlag = true;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(isFlag){
int count = this.getChildCount();
float scaleX = UIUtils.getInstance(this.getContext()).getHorizontalScaleValue();
float scaleY = UIUtils.getInstance(this.getContext()).getVerticalScaleValue();
Log.i("testbarry","x系数:"+scaleX);
Log.i("testbarry","y系数:"+scaleY);
for (int i = 0;i < count;i++){
View child = this.getChildAt(i);
//代表的是当前空间的所有属性列表
LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
layoutParams.width = (int) (layoutParams.width * scaleX);
layoutParams.height = (int) (layoutParams.height * scaleY);
layoutParams.rightMargin = (int) (layoutParams.rightMargin * scaleX);
layoutParams.leftMargin = (int) (layoutParams.leftMargin * scaleX);
layoutParams.topMargin = (int) (layoutParams.topMargin * scaleY);
layoutParams.bottomMargin = (int) (layoutParams.bottomMargin * scaleY);
}
isFlag = false;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
public class UIUtils {
private Context context;
private static UIUtils utils ;
public static UIUtils getInstance(Context context){
if(utils == null){
utils = new UIUtils(context);
}
return utils;
}
//参照宽高
public final float STANDARD_WIDTH = 720;
public final float STANDARD_HEIGHT = 1232;
//当前设备实际宽高
public float displayMetricsWidth ;
public float displayMetricsHeight ;
private final String DIMEN_CLASS = "com.android.internal.R$dimen";
private UIUtils(Context context){
this.context = context;
//
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//加载当前界面信息
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
if(displayMetricsWidth == 0.0f || displayMetricsHeight == 0.0f){
//获取状态框信息
int systemBarHeight = getValue(context,"system_bar_height",48);
if(displayMetrics.widthPixels > displayMetrics.heightPixels){
this.displayMetricsWidth = displayMetrics.heightPixels;
this.displayMetricsHeight = displayMetrics.widthPixels - systemBarHeight;
}else{
this.displayMetricsWidth = displayMetrics.widthPixels;
this.displayMetricsHeight = displayMetrics.heightPixels - systemBarHeight;
}
}
}
//对外提供系数
public float getHorizontalScaleValue(){
return displayMetricsWidth / STANDARD_WIDTH;
}
public float getVerticalScaleValue(){
Log.i("testbarry","displayMetricsHeight:"+displayMetricsHeight);
return displayMetricsHeight / STANDARD_HEIGHT;
}
public int getValue(Context context,String systemid,int defValue) {
try {
Class<?> clazz = Class.forName(DIMEN_CLASS);
Object r = clazz.newInstance();
Field field = clazz.getField(systemid);
int x = (int) field.get(r);
return context.getResources().getDimensionPixelOffset(x);
} catch (Exception e) {
return defValue;
}
}