1. 什么是屏幕尺寸,屏幕分辨率,屏幕像素密度
1.1屏幕尺寸:
屏幕对角线长度,单位是英寸,比如4.7手机,5寸手机,6寸手机
image.png
1.2屏幕分辨率:
如1920*1080,指在手机屏幕上的像素点个数单位是px,1px=1像素点。
image.png
1.3 屏幕像素密度:
指每英寸上的像素点个数,单位是dpi。像素密度与屏幕尺寸和屏幕分辨率有关。
image.png
2. dp、dip、dpi、sp、px
- dp:Android特有的,意为密度无关像素,Google 发布的 BASELINE(基准线)为 160,以此为基准。1dip=1px或1dp=1px。480 * 320 160dpi 那么这台机器上的1DP会被翻译成1px
800 * 480 240dpi 而这台机器上的1DP会被翻译成1.5px - dip:(Density Independent Pixels)同dp一个意思
- dpi:屏幕像素密度
- sp:(Scale-IndependentPixels)可以根据文字大小首选项自动进行缩放。Google推荐我们使用12sp以上的大小,通常使用12sp,14sp,18sp,22sp,为避免精度损失,建议最好不要使用奇数和小数
- px:构成涂销的最小单位,常说的像素
名称 像素密度范围 图片大小
mdpi 120dp~160dp 48×48px
hdpi 160dp~240dp 72×72px
xhdpi 240dp~320dp 96×96px
xxhdpi 320dp~480dp 144×144px
xxxhdpi 480dp~640dp 192×192px
image.png
在Google官方开发文档中,说明了 mdpi:hdpi:xhdpi:xxhdpi:xxxhdpi=2:3:4:6:8 的尺寸比例进行缩放。例如,一个图标的大小为48×48dp,表示在mdpi上,实际大小为48×48px,在hdpi像素密度上,实际尺寸为mdpi上的1.5倍,即72×72px,以此类推,可以继续往后增加,不过一般情况下已经够用了,这种用来去适配手机和平板之间的图形问题
3. 屏幕适配
- 限定符
限定符分类:
屏幕尺寸
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 和标准屏幕配置一样的屏幕宽高比
3.1、 屏幕分辨率限定符(不推荐)
屏幕分辨率限定符适配需要再res文件夹下创建各种屏幕分辨率对应的valuse-xxx,如图
然后根据一个基准分辨率,例如基准1280*720,将宽度分成720份,取值1px720px,将高度分为1280份,取值1px1280px,生成各种分辨率对应的dimens.xml
image.png
加上设计图上空间宽度为600px,那布局中就写android:layout_width="@dimen/x600" ,当运行程序时,就会找对应的valuse下面对应的dimens,如720手机找720下的x600,1080则找1080下的x600,从而达到屏幕的适配。
3.2 smallestWidth 限定符(推荐)
smallestWidth 限定符适配原理与屏幕分辨率限定符适配原理一样,系统都是根据限定符去寻找对应的 dimens.xml 文件。例如程序运行在最小宽度为 360dp 的设备上,系统会自动找到对应的 values-sw360dp 文件夹下的 dimens.xml 文件。区别就在于屏幕分辨率限定符适配是拿 px 值等比例缩放,而 smallestWidth 限定符适配是拿 dp 值来等比缩放而已。需要注意的是“最小宽度”是不区分方向的,即无论是宽度还是高度,哪一边小就认为哪一边是“最小宽度”。如下分别为最小宽度为 360dp 与最小宽度为 640dp 所对应的 dimens.xml 文件:
最小宽度的计算
假如当前设备为1920*1080分辨率 5.5寸手机
1920*1920+1080*1080 = 4852800
开根约2202...
像素密度DPI=2202/5.5=400
400/160 = 2.5 因为160是基准
1080/2.5 = 432 这就是最小宽度dp
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int heightPixels = ScreenUtils.getScreenHeight(this);
int widthPixels = ScreenUtils.getScreenWidth(this);
float density = dm.density;
float heightDP = heightPixels / density;
float widthDP = widthPixels / density;
float smallestWidthDP;
if(widthDP < heightDP) {
smallestWidthDP = widthDP;
}else {
smallestWidthDP = heightDP;
}
image.png
3.3 为什么选择samallWidth限定符
既然原理都一样,都需要多套dimens.xml文件,为啥选择samallWidth不选择屏幕分辨率限定符呢?
1、屏幕分辨率限定符适配根据分辨率的,android设备分辨率非常多,还要考虑虚拟键盘,就需要大量dimens.xml,就19201080是无虚拟键盘,有无虚拟键盘就变成18121080。而无论手机像素是多少,密度多少,90%手机的最小宽度都是360dp所以采用samallWidth,就变成少量dimens.xml文件。
2、屏幕分辨率是px单位,而samallWidth是dp和sp,Google推荐使用dp和sp,sp又能随着设置系统大小而更改字体大小,使用起来更灵活。
3、屏幕分辨率需要valuse-xxx完全匹配才能达到适配,而samallWidth限定符寻找dimens.xml的原理是从大忘小的找,假如设备最小宽度是411dp,就会先找valuse-411dp,发现没有,才会像下找valuse-410~~400,如果没有,才会去valuse默认里面找。
4. 屏幕适配解决方案
4.1 系统布局方案
- 避免写死控件尺寸,使用wrap_content, match_parent
- LinearLayout xxx:weight="0.5“
- RelativeLayout xxx:layout_centerInParent="true" ...
- ContraintLayout xxx:layout_constraintLeft_toLeftOf="parent"...
4.2 像素密度适配
1、在BaseActivity方法中调用DensityUtils.setDensity(getApplication(), this);
2、或者在Activity的生命周期中调用
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
DensityUtils.setDensity(MyApplication.this, activity);
}
下面为像素密度的适配代码
private static final float WIDTH = 360;//参考像素密度(dp)
private static float appDensity;//表示屏幕密度
private static float appScaleDensity;//字体缩放比例,默认为appDensity
public static void setDensity(final Application application, Activity activity) {
//获取当前屏幕信息
DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
if (appDensity == 0) {
//初始化赋值
appDensity = displayMetrics.density;
appScaleDensity = displayMetrics.scaledDensity;
//监听字体变化
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
//字体发生更改,重新计算scaleDensity
if (newConfig != null && newConfig.fontScale > 0) {
appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
//计算目标density scaledDensity
float targetDensity = displayMetrics.widthPixels / WIDTH;//1080/360=3;
float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
int targetDensityDpi = (int) (targetDensity * 160);
//替换Activity的值
//px = dp * (dpi / 160)
DisplayMetrics dm = activity.getResources().getDisplayMetrics();
dm.density = targetDensity; //(dpi/160) 后得到的值
dm.scaledDensity = targetScaleDensity;
dm.densityDpi = targetDensityDpi; //dpi
}
4.3 通过smallestWidth 限定符来适配
5 刘海屏适配
1、判断有没有刘海屏,需要再View绑定到Window后才能判断,没有绑定则拿不到数据
NotchUtil.isHasCutout(this, new OnCutoutListener() {
@Override
public void isHasCutout(boolean isHas) {
isHasCutout = isHas; //注释1
if (isHas) {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
/**
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 全屏模式,内容下移 非全屏模式下不受影响
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 允许内容进入刘海区域
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 不允许内容进入刘海区域
*/
NotchUtil.setImmersiveWithNotch(BaseActivity.this, false,
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
}
}
});
可以在注释1处获取到当前是否是刘海屏
判断刘海屏实际代码
因为刘海屏是在28之后才出现的,Google在28之后也出了专门获取刘海屏的类
/**
* 判断是否有刘海屏
* PS:因为Android P是在回调中判断的,所以,使用listener形式
* 并且,只有在切换时才会获取到,否则返回都是false(API 28)
*
* @param app {@link AppCompatActivity}
* @param listener {@link OnCutoutListener}
*/
public static void isHasCutout(AppCompatActivity app, OnCutoutListener listener) {
if (Build.VERSION.SDK_INT >= 28) {
NotchThirdUtil.getNotchSize4Google(app, listener);
} else {
if (OSUtil.isEmui()) {
listener.isHasCutout(NotchThirdUtil.hasNotchInScreenAtHuawei(app));
} else if (OSUtil.isOppo()) {
listener.isHasCutout(NotchThirdUtil.hasNotchInScreenAtOppo(app));
} else if (OSUtil.isVivo()) {
listener.isHasCutout(NotchThirdUtil.hasNotchInScreenAtVoio(app));
} else if (OSUtil.isMiui()) {
listener.isHasCutout(NotchThirdUtil.hasNotchInScreenAtXiaomi());
}
}
}
2、在得到是否有刘海屏后,如果有,则通过设置布局的padding属性,将布局下移状态栏的高度,没有则不处理
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
if(getIsHasCutout()){ //有刘海屏,则下移
RelativeLayout layout = findViewById(R.id.layout);
layout.setPadding(0, StatusBarUtil.getStatusBarHeight(this)
, 0, 0);
}
}
网友评论