AndroidUtilCode
AndroidUtilCode copied to clipboard
「Android 屏幕适配终结者」问题汇总
文章地址
发现 BUG 的话首先试试升级到最新版本看是否解决了哦。
MIUI 在 Android 5.1.1 情况适配失效
MIUI 自己封装了一层 Resource
,需要去修改它内部的 mTmpMetrics
来适配,这个 BUG 应该是基于修改 DisplayMetrics
都会存在的,升级到 1.22.6 版本即可。
AdaptScreenUtils提供适配宽度和高度2个方法,1080x1920尺寸,适配宽度1080,那View的高度是自动适配1920的高度吗?
@Sum-sdl 适配是在某一维度上进行适配,另一维度一般是可滚动的,如果像你这么说两个纬度都固定了,那怎么可能做到适配,比如你宽度适配了,你又要适配高度,那不同宽高比的手机在某一维度肯定会存在变形的。
适配的话,需要把项目上的dp单位都改成pt吗?
@jingzz1 不需要的啊,你从前的布局就别改动了,新的可以适配进来,老的你想改那也可以
感谢 BlankJ 老师的分享,AndroidAutoSize 已经迭代 10 多个版本,扩展了很多自定义功能,基本能满足所有人的屏幕适配需求,唯一美中不足的就是屏幕适配在某些情况下可能会失效的问题,这一直困扰着 AndroidAutoSize 以及今日头条屏幕适配方案,之前我对于此问题提供了两种解决思路
第一个就是在布局显示到屏幕上之前,调用框架提供的方法将 DisplayMetrics 的参数恢复成期望的值,但必须保证调用这个方法到布局显示到屏幕的期间,DisplayMetrics 的参数不能被修改,由于某些定制系统的行为可能是未知的,所以就一直没找到一个最合适并且通用的调用时机
第二个就是寻求获得唯一修改 DisplayMetrics 的权限,让其他代码不能修改 DisplayMetrics,但这也只是构想,还没找到比较好的解决方案
今天看到 blankJ 老师的文章,恍然大悟,我第一个方案一直在寻找的最合适的调用时机,可能就是您的方案!
今日头条方案的切入点 TypedValue.applyDimension(int unit, float value, DisplayMetrics metrics) 方法,需要传入一个 DisplayMetrics,而系统会在调用 TypedValue.applyDimension 之前通过 context.getResources() 获取 DisplayMetrics,所以重写 getResources 将 DisplayMetrics 的参数修改为正确的值,这样就可以极大的避免了 DisplayMetrics 的参数在布局显示到屏幕上之前被修改,我之前还是想太复杂,舍近求远,没有关注到这个这个核心切入点
不过这里还有一个疑虑,如果在调用 TypedValue.applyDimension 到布局显示到屏幕的期间,DisplayMetrics 的参数被其他未知代码修改,也还是会造成屏幕适配的失效,不过您的解决方案已经可以让稳定性提升一大步
最后感谢 BlankJ 老师的分享,文中的总结很到位,我们都是站在巨人的肩膀上,感谢开源,我们一起努力将会让今日头条屏幕适配方案更完美!
pt!=dp 为什么说关闭这个效果pt的效果==dp 这点不是很明白
@45541926 所谓的关闭我还是对 pt 操作了,又不是原生的 1pt = 1/72 inch
@45541926 所谓的关闭我还是对 pt 操作了,又不是原生的 1pt = 1/72 inch
哦哦 懂了 ths
果然强大呀,但有一事不解,如下代码:
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;
}
不知为什么挑了 PT
呢,用 IN
不是还省的 *72
了?
pt 比较接近于 dp,inch、mm 都太大了
@lcl6 你说的是什么意思?上个图吧,textview 内容无法对齐和我这适配有什么关系?是你自己设置的 gravity 不同啊
如果是悬浮窗适配,因为 inflate 用到的 context 是 application 级别的,所以需要在自定义的 Application 中重写 getResource。参考 https://github.com/Blankj/AndroidUtilCode/issues/840
单个维度适配的话,那在一些全面屏手机上比如:1080*2280这个种情况下高度,控件的高度就变小了,有什么办法可以解决吗?
单纬度是因为有一维度是可滚动的啊,怎么会变小呢,我 demo 有变小? 双维度适配岂不是只有一种屏幕比例啊,比如只有 16 : 9 比例,那你在 4 : 3 机器上肯定会变形啊。
您好,问一下,如果我要在pad上适配的话,会有什么问题么
@mufaith 和设备没关系的,只要是 android 就行
屏幕预览30寸怎么解决啊?
@luyun181 文章中新建设备后选新建的设备来预览,已经写清楚了哈
@Blankj 我的意思是Android studio中选了新建的设备,新建的设备是30寸的,预览显示的就是30寸屏幕大小。Android studio版本是3.3.2
这说明你没用 pt 哈
@Override public Resources getResources() { return AdaptScreenUtils.adaptWidth(super.getResources(), 750); // return AdaptScreenUtils.adaptHeight(super.getResources(),1334); }
我把这个写在baseActivity中了。
@luyun181 那你控件尺寸要用 pt 才会显现正常尺寸哦
小米6 MIUI 10.3 版本 ,不好使 ,各位有没有遇到过? 还是已解决 ,求告知。
@ddqiang 试试这个呢
import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public final class AdaptScreenUtils {
private static List<Field> sMetricsFields;
/**
* Adapt for the horizontal screen, and call it in [android.app.Activity.getResources].
*/
public static Resources adaptWidth(final Resources resources, final int designWidth) {
float newXdpi = (Resources.getSystem().getDisplayMetrics().widthPixels * 72f) / designWidth;
applyDisplayMetrics(resources, newXdpi);
return resources;
}
/**
* Adapt for the vertical screen, and call it in [android.app.Activity.getResources].
*/
public static Resources adaptHeight(final Resources resources, final int designHeight) {
float newXdpi = (Resources.getSystem().getDisplayMetrics().heightPixels * 72f) / designHeight;
applyDisplayMetrics(resources, newXdpi);
return resources;
}
/**
* @param resources The resources.
* @return the resource
*/
public static Resources closeAdapt(final Resources resources) {
float newXdpi = Resources.getSystem().getDisplayMetrics().density * 72f;
applyDisplayMetrics(resources, newXdpi);
return resources;
}
/**
* Value of pt to value of px.
*
* @param ptValue The value of pt.
* @return value of px
*/
public static int pt2Px(final float ptValue) {
DisplayMetrics metrics = Utils.getApp().getResources().getDisplayMetrics();
return (int) (ptValue * metrics.xdpi / 72f + 0.5);
}
/**
* Value of px to value of pt.
*
* @param pxValue The value of px.
* @return value of pt
*/
public static int px2Pt(final float pxValue) {
DisplayMetrics metrics = Utils.getApp().getResources().getDisplayMetrics();
return (int) (pxValue * 72 / metrics.xdpi + 0.5);
}
private static void applyDisplayMetrics(final Resources resources, final float newXdpi) {
resources.getDisplayMetrics().xdpi = newXdpi;
Utils.getApp().getResources().getDisplayMetrics().xdpi = newXdpi;
applyOtherDisplayMetrics(resources, newXdpi);
}
private static void applyOtherDisplayMetrics(final Resources resources, final float newXdpi) {
if (sMetricsFields == null) {
sMetricsFields = new ArrayList<>();
Class resCls = resources.getClass();
Field[] declaredFields = resCls.getDeclaredFields();
while (declaredFields != null && declaredFields.length > 0) {
for (Field field : declaredFields) {
if (field.getType().isAssignableFrom(DisplayMetrics.class)) {
field.setAccessible(true);
DisplayMetrics tmpDm = getMetricsFromField(resources, field);
if (tmpDm != null) {
sMetricsFields.add(field);
tmpDm.xdpi = newXdpi;
}
}
}
resCls = resCls.getSuperclass();
if (resCls != null) {
declaredFields = resCls.getDeclaredFields();
} else {
break;
}
}
} else {
applyMetricsFields(resources, newXdpi);
}
}
private static void applyMetricsFields(final Resources resources, final float newXdpi) {
for (Field metricsField : sMetricsFields) {
try {
DisplayMetrics dm = (DisplayMetrics) metricsField.get(resources);
if (dm != null) dm.xdpi = newXdpi;
} catch (Exception e) {
Log.e("AdaptScreenUtils", "applyMetricsFields: " + e);
}
}
}
private static DisplayMetrics getMetricsFromField(final Resources resources, final Field field) {
try {
return (DisplayMetrics) field.get(resources);
} catch (Exception e) {
Log.e("AdaptScreenUtils", "getMetricsFromField: " + e);
return null;
}
}
}
@Blankj 问题解决!
小米5x,切到后台,一段时间后,打开。pt失效
看了https://juejin.im/post/5b6250bee51d451918537021这篇文章过来的,发现没有adaptScreen4VerticalSlide 可以调用,请问是修改了吗?
@pokerfaceCmy 看 issue 中写的文章
@Blankj 文字也要写pt吗,写pt会有问题吗?
AdaptScreenUtils对RecyclerView和ToolBar的如何适配呢
textview的drawableLeft 这种drawable怎么跟着适配
@Kunsan16 这种 drawable 是看你放在哪个分辨率的文件夹下的吧,你这种想改变大小就动态设置 bounce 吧,然后用 pt2Px 即可
@l0702040115 直接写 pt 就行啊
@li-xiaojun 看你文字是否要跟随系统字体大小改变啊,需要的话就写 sp,不需要改变就直接 pt 就行
我这里需要适配长边/短边(旋转屏幕后控件的大小不会发生变化),而不是适配当前宽度/高度。所以自己建了个repo,拉了你的工具类修改了下,不知道大佬你介不介意(当然是有放引用来源)。
/**
* 针对屏幕“较短边”进行适配,重写{@link Activity#getResources()} 方法,调用此方法后返回
*
* @param resources 填入super.getResources()
* @param designShortSize 参考UI图的较短边尺寸
*/
public static Resources adaptShorter(Resources resources, int designShortSize) {
return adaptShorter(resources, designShortSize, false);
}
/**
* 针对屏幕“较短边”进行适配,重写{@link Activity#getResources()} 方法,调用此方法后返回
*
* @param resources 填入super.getResources()
* @param designShortSize 参考UI图的较短边尺寸
* @param includeNavBar 是否包含导航栏
*/
public static Resources adaptShorter(Resources resources, int designShortSize,
boolean includeNavBar) {
DisplayMetrics dm = resources.getDisplayMetrics();
int shorter = dm.widthPixels < dm.heightPixels ? dm.widthPixels : dm.heightPixels;
float shorterWithNav =
shorter * 72f + (includeNavBar ? AdaptScreenUtils.getNavBarHeight(resources) : 0);
float newXdpi = shorterWithNav / designShortSize;
AdaptScreenUtils.applyDisplayMetrics(resources, newXdpi);
return resources;
}
/**
* 针对屏幕“较长边”进行适配,重写{@link Activity#getResources()} 方法,调用此方法后返回
*
* @param resources 填入super.getResources()
* @param designLongerSize 参考UI图的较长边尺寸
*/
public static Resources adaptLonger(Resources resources, int designLongerSize) {
return adaptLonger(resources, designLongerSize, false);
}
/**
* 针对屏幕“较长边”进行适配,重写{@link Activity#getResources()} 方法,调用此方法后返回
*
* @param resources 填入super.getResources()
* @param designLongerSize 参考UI图的较长边尺寸
* @param includeNavBar 是否包含导航栏
*/
public static Resources adaptLonger(Resources resources, int designLongerSize,
boolean includeNavBar) {
DisplayMetrics dm = resources.getDisplayMetrics();
int longer = dm.widthPixels > dm.heightPixels ? dm.widthPixels : dm.heightPixels;
float longerWithNav =
longer * 72f + (includeNavBar ? AdaptScreenUtils.getNavBarHeight(resources) : 0);
float newXdpi = longerWithNav / designLongerSize;
AdaptScreenUtils.applyDisplayMetrics(resources, newXdpi);
return resources;
}
使用pt单位做适配的时候 有时适配会失效 尤其在列表控件使用时
@inshyma 什么版本,失效是页面在后台久了还是刚进去久失效?问题描述清楚一点哈
@Blankj 1.24.0 触发失效的条件没找到. 目前发现是在一个页面的RecyclerView的item里面 ; 页面onResume 或者Fragment 重新切回来 可能会出现这种情况 AdaptScreenUtils.adaptWidth调用位置在onCreate中
@inshyma 不是说让你放在 getResource 里么
@Blankj 哦 我试试
在一个activity页面(添加了 android:configChanges="orientation|screenSize|keyboardHidden")的RecyclerView的item里使用,当旋转屏幕触发onConfigurationChanged的时候,某些item出现尺寸适配失效。
@nwhhades 是正常写在 getResource 里的吗?
@Blankj 对的
不旋转屏幕的时候都是正常,触发onConfigurationChanged后个别item出现失效
@nwhhades @licheedev 旋转屏幕的话因为之后的 width 变成了 height,所以反一下即可。
@Override
public Resources getResources() {
if (ScreenUtils.isPortrait()) {
return AdaptScreenUtils.adaptWidth(super.getResources(), 1080);
} else {
return AdaptScreenUtils.adaptHeight(super.getResources(), 1080);
}
}
@Blankj drawable文件夹下的资源文件写pt会被适配吗 我似乎没有找到这个相关的说明 也许是我粗心了
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="12pt" />
<padding
android:bottom="4pt"
android:left="8pt"
android:right="8pt"
android:top="4pt" />
<solid android:color="#44aaaaaa" />
</shape>
它的 View getResource 是适配后的那就行呀,也就是你开启了适配,那就有效
@Blankj
它的View getResource是适配后的那就行呀,也就是你开启了适配,那就有效
它的 View getResource 是适配后的 是指该View的getResource方法做了适配的就可以是吗
如果这个getResource适配写在activity 那么只要是属于这个activity context下的所有view只要是pt单位的就会得到适配吗 适配后他的自定义属性 或者background引用的资源内使用的pt单位 或者是 所有为pt的单位的属性都会得到适配?(自定义view的自定义属性取值要怎么做 pt2px就行吗?) 😁
@Blankj
它的View getResource是适配后的那就行呀,也就是你开启了适配,那就有效
它的 View getResource 是适配后的 是指该View的getResource方法做了适配的就可以是吗
如果这个getResource适配写在activity 那么只要是属于这个activity context下的所有view只要是pt单位的就会得到适配吗 适配后他的自定义属性 或者background引用的资源内使用的pt单位 或者是 所有为pt的单位的属性都会得到适配?(自定义view的自定义属性取值要怎么做 pt2px就行吗?) 😁
应该是的,你可以实测下哈
@Blankj ConverUtils中的dp2px,px2dp等使用的都是系统的DisplayMetrics,改成应用自己的是不是比较好,否则如果使用了修改density的适配方案,这里依然是按照旧的density进行转换的
@walkthehorizon 不推荐 dp 适配,会导致系统 View 大小出问题。
![WX20190903-175917@2x](https://user-images.githubusercontent.com/54034989/64163879-a61da680-ce74-11e9-9056-4a1d1d814309.png)
![WX20190903-175944@2x](https://user-images.githubusercontent.com/54034989/64164160-3cea6300-ce75-11e9-801e-8f83f7f9a26b.png)
@wsyzj92 如果你字体不想随系统字体改变而改变,那字体大小用 pt 即可,如果想采用 sp 的话,那就用 tools:textSize="xxpt"
来预览即可。
@Blankj 好的,明白了。
字体采用pt不能随系统改变,采用sp那不就是等于说字体不适配吗?这个很重要吧
@rube520 谁规定应用字体就一定要用 sp 了,sp 只是能根据系统字体大小而改变罢了,有些应用就不想要这个随系统改变的功能,改变字体了 view 布局就出问题了,想用什么自己选择就好
重写application的getResources()方法会导致栈溢出。 @Override public Resources getResources() { //以pt为单位进行适配 if (ScreenUtils.isLandscape(this)){ return AdaptScreenUtils.adaptWidth(super.getResources(), 640, this); } else { return AdaptScreenUtils.adaptHeight(super.getResources(), 640, this); } // return super.getResources(); }
感谢作者的开源库,关于屏幕适配我请教几个问题: 1、设计用的蓝湖,设计图是375667,计算(375375+667*667)开平方再除72约等于10.63,density为ldpi的,那是不是后面的icon资源需要下载对应ldpi尺寸,然后放在项目res的ldpi目录下? 2、接上个问题,只需要一套ldpi资源可以了吗? 3、开发的时候使用pt单位,如果后期需要更换其他适配方案,是不是每个xml的pt都需要修改?
@magic0908 资源都是用一套尽可能高的 dpi 的呀,适配是百分比适配,你传入了适配宽度是 375,那你写 375 pt ,那在任何机器上展示都是全屏的宽度,你写 100pt,那占的就是 100/375 的宽度百分比,你要布局要用其他适配,那你就别用 pt 就行了,你用 dp 那些对 pt 都不会有影响。
为什么我单位只有设置成pt才生效是否,其他的适配都不生效。设置是按照教程的1080*1920设置的
@qinlei00 好好看文章,文章里都说了是用 pt 适配,那你用其他单位肯定没啥用的
Application中重写getResources( )存在递归调用
重复一个上面提问过的问题,但可能没有这里描述的清楚。就是我们创建好设备之后,预览选择自己创建的设备,如果字体不设置textSize,宽高设置自适应,预览图上就会很小,其实这是因为系统默认给TextView的textSize设置的大小是15sp,导致在预览图上看起来很小。但是有时候我们想用系统默认的大小导致预览图与实际效果差很多,不知道作者有没有什么好的想法或者解决办法,是不是必须得给TextView设置成pt。例如还有时候我们自定义标题栏,高度一般填写?attr/android:actionBarSize,这时候这种高度预览起来也会很小。
在一个activity页面(添加了 android:configChanges="orientation|screenSize|keyboardHidden")的RecyclerView的item里使用,当旋转屏幕触发onConfigurationChanged的时候,某些item出现尺寸适配失效。
@nwhhades 解决了吗
页面A 指定了横屏,但是进入之后 会先进入页面B(竖屏),从B返回了A,这时候,A适配错乱了,这种情况有解决方案么?
在一个activity页面(添加了 android:configChanges="orientation|screenSize|keyboardHidden")的RecyclerView的item里使用,当旋转屏幕触发onConfigurationChanged的时候,某些item出现尺寸适配失效。
@nwhhades 解决了吗 我也遇到了这个问题,解决了么
适配后,用的一些三方库单位还是dp,会显得很小,怎么解决? @Blankj
适配后,当app页面在横屏模式下,按下home键,app在后台运行时刷新ui,会适配失效,@Blankj 已解决 ScreenUtils.isPortrait()在app进入后台后,横屏页面获取依然返回的true导致 适配判断横竖屏应用Activity里的Resources配置里的屏幕方向 @Override public Resources getResources() { if (isPortrait()) { return AdaptScreenUtils.adaptWidth(super.getResources(), InitManager.BASE_SIZE_IN_DP); } else { return AdaptScreenUtils.adaptHeight(super.getResources(), InitManager.BASE_SIZE_IN_DP); } }
public boolean isPortrait() {
return super.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
}
Pada tanggal Jum, 8 Jan 2021 13:43, Meng [email protected] menulis:
适配后,当app页面在横屏模式下,按下home键,app在后台运行时刷新ui,会适配失效
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/Blankj/AndroidUtilCode/issues/758#issuecomment-756581465, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQQT5NBDKYKU6AIGE4ORPU3SY2SSPANCNFSM4GK377AA .
蓝湖上设计图尺寸 dp 和 pt 都是 414 ✖️ 896,px是1242 ✖️ 2688。我现在页面用dp,字体用sp,图片放在xxxhdpi 。那么我接入适配用 AdaptScreenUtils.adaptWidth(super.getResources(), 414) 还是 AdaptScreenUtils.adaptWidth(super.getResources(), 1242) 我两个都试了,效果一样😂 还有接入后单位还可以用dp sp吗,还是统一用 pt 谢谢🌹
用这个方法适配的app,在华为多任务的小窗口上适配失效
@bytebubbles 用作者Demo APP试过 确实是失效的,
老哥,请教一个问题。 如果我想自定义系统的布局选择 例如:layout-land 、layout-port 。 我自定义layout-xxx ,并定义屏幕尺寸的选择规则,请问要从哪方面入手呢?
用这个方法适配的app,在华为多任务的小窗口上适配失效
这个问题现在有解决方案不? (华为小窗口情况下,resources.getDisplayMetrics() 每次返回的对象都是不同的,所以无效。)