-
2020-09-22 09:33:40
Android 第三方库系列文章
博客创建时间:2020.09.20
博客更新时间:2021.06.27
以Android studio build=4.2.1,gradle=6.7.1,SdkVersion 30来分析讲解。如图文和网上其他资料不一致,可能是别的资料版本较低而已
前言
首先感谢大神JessYan的创神之作《AndroidAutoSize》,大神以今日头条屏幕适配的核心代码为基础进行了扩展封装,产生了《AndroidAutoSize》这个能快速接入使用的屏幕适配方案,这个屏幕适配方案是我遇到的截止2020.9.15为止最强大、简单有效的屏幕适配方案。我已使用该方案有一年,在使用过程未发现有何问题,强烈推荐各位极客们使用学习。
以下是大神JessYan的相关地址:
- 邮箱:jess.yan.effort@gmail.com
- github:https://github.com/JessYanCoding/AndroidAutoSize
- 简书:https://www.jianshu.com/p/4aa23d69d481
- 原始核心代码:https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
大神的源码都在github中各位可以自行下载,我写这篇博客的目的就是记录使用心得,并将该框架的重要类和方法使用进行详细说明,大神的文章中对细节问题写的比较少,我对其进行了细微的修改和详细的注解解释。我自己修改注释过得框架源码请前往github下载https://github.com/l424533553/MyAutoSize。
1. 屏幕像素
像素
通常所说的像素,就是CCD/CMOS上光电感应元件的数量,一个感光元件经过感光,光电信号转换,A/D转换等步骤以后,在输出的照片上就形成一个点,我们如果把影像放大数倍,会发现这些连续色调其实是由许多色彩相近的小方点所组成,这些小方点就是构成影像的最小单位“像素”(Pixel)。简而言之,像素就是手机屏幕的最小构成单元。屏幕尺寸
屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米。比如常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等屏幕分辨率
屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1个像素点。一般以纵向像素横向像素,如19201080屏幕像素密度(dpi)
屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。
计算公式: 像素密度 = 像素 / 尺寸 (dpi = px / in)
标准屏幕像素密度(mdpi): 每英寸长度上还有160个像素点(160dpi),即称为标准屏幕像素密度(mdpi)。密度无关像素(dp)
含义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关
单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果,是安卓特有的长度单位。
场景例子:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分辨率手机上设置应为240px;在320x480的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。
dp与px的转换:1dp = (dpi / 160 ) * 1px;密度类型 代表的分辨率(px) 屏幕像素密度(dpi) 换算 低密度(ldpi) 240 x 320 120 1dp = 0.75px 中密度(mdpi) 320 x 480 160 1dp = 1px 高密度(hdpi) 480 x 800 240 1dp = 1.5px 超高密度(xhdpi) 720 x 1280 320 1dp = 2px 超超高密度(xxhdpi) 1080 x 1920 480 1dp = 3px 独立比例像素(sp)
scale-independent pixel,叫sp或sip,字体大小专用单位 ,Android开发时用此单位设置文字大小,可根据字体大小首选项进行缩放。
推荐使用12sp、14sp、18sp、22sp作为字体大小,不推荐使用奇数和小数,容易造成精度丢失,12sp以下字体太小。sp与dp的区别
dp只跟屏幕的像素密度有关, sp和dp很类似但唯一的区别是,Android系统允许用户自定义文字尺寸大小(小、正常、大、超大等等),当文字尺寸是“正常”时1sp=1dp=0.00625英寸,而当文字尺寸是“大”""或“超大”时,1sp>1dp=0.00625英寸。类似我们在windows里调整字体尺寸以后的效果——窗口大小不变,只有文字大小改变。
2. 适配原理
传统的屏幕适配头如下几种措施:
- 种像素密度机型,做5套图。比例 1:1.5:2:3:4
- 多用相对布局
- 尺寸限定符
- 点九图
- 不同图片填充类型ScaleType
但是以往的所有屏幕适配都有各种各样的问题和重大缺陷,直到字节跳动的屏幕适配方案出现。根据其公开的核心源码,网上重大大咖封装了各种屏幕适配框架,其中最成功且本人使用感受最好的是AutoSize框架。
Android AutoSize的核心代码来源于字节跳动的微信文章https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA。网上也有多各个大神进行了代码的封装设计,都是万变不离其中。
1. 核心思想
DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics(); if (sNoncompatDensity == 0) { sNoncompatDensity = appDisplayMetrics.density; sNoncompatDensity = appDisplayMetrics.scaledDensity; application.registerComponentCallbacks(new ComponentCallbacks() { public void onConfigurationChanged(Configuration newConfig) { if (newConfig != null && newConfig.fontScale > 0) { sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity; } } public void onLowMemory() { } }); } float targetDensity = appDisplayMetrics.widthPixels / 360; float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity); int targetDensityDpi = (int) (160 * targetDensity); appDisplayMetrics.density = targetDensity; appDisplayMetrics.scaledDensity = targetScaleDensity; appDisplayMetrics.densityDpi = targetDensityDpi; final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics(); activityDisplayMetrics.density = targetDensity; activityDisplayMetrics.scaledDensity = targetScaleDensity; activityDisplayMetrics.densityDpi = targetDensityDpi;
原理很简单,例如一个4.59的10801920的手机它的dpi=480,它的density=480/160,则说明1dp=3px。当我们在布局中给如TextView设置layout_width=30dp时,在程序运行时会自动计算其对应px单位长度90px,px=dpdensity。
今日头条适配方案的核心就是动态计算程序中的density=appDisplayMetrics.widthPixels / 360,360是原始设计图纸的dp。假设原先的设计图纸10801920,现在适配5.99寸560dpi的14402880手机,则30dp=30560/160=105px,实际上屏幕适配要求的30dp=1440/36030=120px才可以达到适配效果。因为120/1440=90/1080,控件在布局中的占宽比是一样的才能达到宽度适配效果。这就是为什么要动态修改全局或activity的DisplayMetrics#density的目的了。
2. 优缺点
- 优点:
- 侵入性非常低,该方案和项目完全解耦,使用的还是Android官方单位
- 接入无性能损耗,使用的全是Android官方的API。
- 缺点:
- 项目中的系统控件、三方库控件、等非我们项目自身设计的控件,它们的设计图尺寸并不会和我们项目自身的设计图尺寸一样,此时会产生适配误差。解决方案就是取消当前 Activity 的适配效果,改用其他的适配方案
- 系统修改字体大小后,返回应用系统字体大小还是未改变,需要设置registerComponentCallbacks监听。 Android AutoSize框架已经解决了该问题。
- 在使用过程中需要进行registerComponentCallbacks监听内容文字的大小改变情况,解决退出应用修改文字大小后,文字大小不改变的情况。
3. 框架配置
依赖配置
- 远程依赖,截止到2020.9.15,版本为1.2.1
implementation 'me.jessyan:autosize:1.2.1'
- 从github上下载源码进行library库依赖
参数配置
在AndroidManifest.xml中配置参数<manifest> <application> ... <meta-data android:name="design_width_in_dp" android:value="360"/> <meta-data android:name="design_height_in_dp" android:value="640"/> ... </application> </manifest>
4. 自定义初始化
本文中使用的框架是经过大神JessYan的封装后成为你所看到的框架。它能根据一套给定的设计图尺寸进行布局展示,当安装当不同分辨率尺寸的设备上时,它能自动适配屏幕。
框架的初始化时机是配置在ContentProvider中,在Application#onCreate()方法之前启动。框架一旦初始化完成,其适配效果会在Activity和Fragment、各种View中自动全局适配。程序将默认是以屏幕宽度为基准进行适配的,并且使用的是在AndroidManifest中填写的全局设计图尺寸进行全局适配。
框架支持dp、sp两个主单位,pt、in、mm三个冷门副单位,如果使用副单位,可以规避系统控件或三方库控件使用的不良影响。
ContentProvider初始化第三方库
ContentProvider是一种共享型组件,它通过Binder向其他组件或者其他应用程序提供数据,当ContentProvider所在进程启动时候,ContentProvider会被同时启动并被发布到AMS中。ContentProvider的onCreate要优先于Application的onCreate,但在attachBaseContext()之后而执行,它的具体详细启动源码在ActivityThread中。很多人会在ContentProvider#onCreate()初始化第三方库。
一般进行了依赖配置和参数配置两操作,Android AutoSize就配置完成可以直接使用了,它的框架源码初始化在InitProvider代码中。
在InitProvider 中已进行了初始化设置
public class InitProvider extends ContentProvider { @Override public boolean onCreate() { if (getContext() != null) { Context application = getContext().getApplicationContext(); if (application == null) { application = AutoSizeUtils.getApplicationByReflect(); } AutoSizeConfig.getInstance() .setLog(true) .init((Application) application) .setUseDeviceSize(false); return true; } return false; }
但是为了个性化的配置,我们可以在Application中进行一些自定义设置,设置的方法都应写在Application#onCreate()方法中。
public class Application { @Override public void onCreate() { super.onCreate(); ... AutoSize.initCompatMultiProcess(this); AutoSize.checkAndInit(this); AutoSizeConfig.getInstance() .setCustomFragment(true) .setExcludeFontScale(true) .setPrivateFontScale(0.8f) .setLog(false) .setBaseOnWidth(true) .setUseDeviceSize(true) //屏幕适配监听器 .setOnAdaptListener(new OnAdaptListener() { @Override public void onAdaptBefore(Object target, Activity activity) { // AutoSizeConfig.getInstance().setScreenWidth(ScreenUtils.getScreenSize(activity)[0]); // AutoSizeConfig.getInstance().setScreenHeight(ScreenUtils.getScreenSize(activity)[1]); AutoSizeLog.d(String.format(Locale.ENGLISH, "%s onAdaptBefore!", target.getClass().getName())); } @Override public void onAdaptAfter(Object target, Activity activity) { AutoSizeLog.d(String.format(Locale.ENGLISH, "%s onAdaptAfter!", target.getClass().getName())); } }); configUnits(); } private void configUnits() { AutoSizeConfig.getInstance() .getUnitsManager() .setSupportDP(true) .setDesignSize(2160, 3840) .setSupportSP(true) .setSupportSubunits(Subunits.MM); } }
5. 常用方法解析
对于初始化中方法,我们进行一一分析
1. AutoSize.initCompatMultiProcess(Context context)
当 App 中出现多进程,并且您需要适配所有的进程,就需要在 App 初始化时调用。一般的单进程App程序不用设置。2. AutoSize.checkAndInit(Application application)
if (!checkInit()) { AutoSizeConfig.getInstance() .setLog(true) .init(application) .setUseDeviceSize(false); }
一般来说Android AutoSize会通过InitProvider实例化自动完成初始化,是不需要调用checkAndInit()方法的。但由于某些 issues 反应, 可能会在某些特殊情况下出现InitProvider未能正常实例化的情况, 导致 AndroidAutoSize 未能完成初始化。所以需要使用该方法确保Android AutoSize 初始化成功。
3. AutoSizeConfig.getInstance().setCustomFragment(boolean customFragment)
设定是否让框架支持自定义Fragment 的适配参数,一般这个需求比较少。默认不支持的4. AutoSizeConfig.getInstance().setExcludeFontScale(true)
是否屏蔽系统字体大小对AndroidAutoSize 的影响, 如果为 true, App 内的字体的大小将不会跟随系统设置中字体大小的改变, 如果为 false, 则会跟随系统设置中字体大小的改变, 默认为 false5. AutoSizeConfig.getInstance().setPrivateFontScale(float fontScale)
区别于系统字体大小的放大比例, AndroidAutoSize 允许 APP 内部可以独立于系统字体大小之外,独自拥有全局调节 APP 字体大小的能力。 fontScale取值0~1,设为 0 则取消此功能。同时字体的单位必须是sp做单位。6. AutoSizeConfig.getInstance().setLog(boolean log)
设置是否打印AutoSize的日志,true为打印7. AutoSizeConfig.getInstance().setBaseOnWidth(true)
是否全局按照宽度进行等比例适配,true以宽来适配,false以高来适配8. AutoSizeConfig.getInstance().stop(this)
自动适配方案可以手动调用方法停止,需要注意的是Android AutoSize暂停只是停止了对后续还没有启动的{@link Activity}进行适配的工作,但对已经启动且已经适配的{@link Activity}不会有任何影响9. AutoSizeConfig.getInstance().restart()
AutoSize可以暂停适配也可以重启适配,但是重启适配只能对后续还没有启动的 {@link Activity} 进行适配的工作,但对已经启动且在stop期间未适配的{@link Activity}不会有任何影响10. AutoSizeConfig.getInstance().setUseDeviceSize(true)
是否以屏幕的实际尺寸为高度,默认为false,屏幕的适配高度是屏幕总高度减去状态栏高度。11. UnitsManager.setSupportSP(boolean supportSP)
是否让框架支持sp单位,默认是为true支持,如果为false,则字体大小最好设置为其他单位才能自动适配12. UnitsManager.setSupportSubunits(Subunits supportSubunits)
自主设置心仪的副单位,可以从pt、in、mm中进行选择,如果使用了Subunits#NONE即代表不支持副单位13. UnitsManager.setSupportDP(boolean supportDP)
是否支持dp单位,默认是true支持,如果关闭将不对dp单位进行支持14. UnitsManager.setDesignSize(float designWidth, float designHeight)
设置设计图尺寸,一般专为副单位尺寸设计,它与AndroidManifest.xml中配置的参数不一样,不会被覆盖。
6. 常见接口及类的使用
CustomAdapt
实现CustomAdapt接口即可对activity和fragment进行新的自定义尺寸适配,适配方向可以自主选择是宽度还是高度。实现该接口会取消默认的适配方案和效果。对于fragment的自定义尺寸需要进AutoSizeConfig.getInstance().setCustomFragment(true)设置,默认是不支持对fragment的自定义尺寸适配的。<在CustomAdapt接口中需要实现者重写两个方法boolean isBaseOnWidth()和float getSizeInDp(),根据使用者需求自定义。
-
1. boolean isBaseOnWidth()
为了保证在高宽比不同的屏幕上也能正常适配,所以只能在宽度和高度之中选一个作为基准进行适配。 true为按照宽度适配, false 为按照高度适配 -
2. float getSizeInDp()
getSizeInDp 须配合isBaseOnWidth()使用, 有如下使用规则:
如果 {@link #isBaseOnWidth()} 返回 {@code true}, {@link CustomAdapt #getSizeInDp} 则应该返回设计图的总宽度。
如果 {@link #isBaseOnWidth()} 返回 {@code false}, {@link CustomAdapt #getSizeInDp} 则应该返回设计图的总高度。
如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸,getSizeInDp 则返回 0即可。
CancelAdapt
接口CancelAdapt没有任何成员变量,支持AndroidAutoSize的项目所有模块默认使用适配功能,第三方库的也不例外。
如果某个页面不想使用适配功能, 请让该页面实现CancelAdapt接口放弃适配,所有的适配效果都将失效。
7.框架核心
1. 自定义适配
通过字节跳动的核心源码,只能进行全局适配,但是该框架中进行了Activity和Fragmen的自定义适配和随时取消恢复适配功能。它的原理是注册了ActivityLifecycleCallbacks,进行了Activity的适配时间精准化自我掌控。通过注册ActivityLifecycleCallbacks,进行Activity的生命周期进行管理, 当onActivityCreated时,也就是OnCreate()的setContentView之前进行了AutoAdaptStrategy#applyAdapt的调用。这种方案类似于 AOP, 面向接口, 侵入性低, 方便统一管理, 扩展性强。
@Override public void onActivityCreated(@androidx.annotation.NonNull Activity activity, Bundle savedInstanceState) { if (AutoSizeConfig.getInstance().isCustomFragment()) { if (mFragmentLifecycleCallbacksToAndroidx != null && activity instanceof androidx.fragment.app.FragmentActivity) { ((androidx.fragment.app.FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacksToAndroidx, true); } } //Activity 中的 setContentView(View) 一定要在 super.onCreate(Bundle); 之后执行 if (mAutoAdaptStrategy != null) { mAutoAdaptStrategy.applyAdapt(activity, activity); } }
通过注册registerFragmentLifecycleCallbacks,进行Fragment的生命周期管理,当onFragmentCreated时,也就是OnCreate()中进行了AutoAdaptStrategy#applyAdapt的调用
@Override public void onFragmentCreated(@androidx.annotation.NonNull FragmentManager fm, @androidx.annotation.NonNull Fragment f, Bundle savedInstanceState) { if (mAutoAdaptStrategy != null) { mAutoAdaptStrategy.applyAdapt(f, f.getActivity()); } }
通过全局的进行Activity和Fragment的生命周期监控,在其布局创建之前调用 AutoAdaptStrategy#applyAdapt进行具体的适配操作,它的关键点是动态修改density、scaledDensity、densityDpi三个参数,造成每个Activity或Fragment加载布局时的density、scaledDensity、densityDpi等参数不一样,达到的适配效果则不一样。
2. 适配策略的实现
ActivityLifecycleCallbacks的使用能实时监测Activity和Fragment进行适配调用,但是实际操作的代码在策略方案AutoAdaptStrategy的实现子类中,框架中已有默认策略方案,当然自己也可以自定义修改创建。- 当target实现CancelAdapt后,将density、scaledDensity、densityDpi恢复到原始状态,不进行匹配
- 当target实现CustomAdapt后,将density、scaledDensity、densityDpi根据target的配置进行计算后设置
- 当target未进行任何处理时,将density、scaledDensity、densityDpi根据AndroidManifest.xml中的配置进行计算设置
@Override public void applyAdapt(Object target, Activity activity) { .... //如果 target 实现 CancelAdapt 接口表示放弃适配, 所有的适配效果都将失效 if (target instanceof CancelAdapt) { AutoSizeLog.w(String.format(Locale.ENGLISH, "%s canceled the adaptation!", target.getClass().getName())); AutoSize.cancelAdapt(activity); return; } //如果 target 实现 CustomAdapt 接口表示该 target 想自定义一些用于适配的参数, 从而改变最终的适配效果 if (target instanceof CustomAdapt) { AutoSizeLog.d(String.format(Locale.ENGLISH, "%s implemented by %s!", target.getClass().getName(), CustomAdapt.class.getName())); AutoSize.autoConvertDensityOfCustomAdapt(activity, (CustomAdapt) target); } else { AutoSizeLog.d(String.format(Locale.ENGLISH, "%s used the global configuration.", target.getClass().getName())); AutoSize.autoConvertDensityOfGlobal(activity); } ... }
8. 其它
1. Fragment横竖屏切换布局问题
由于某些原因, 屏幕旋转后 Fragment 的重建, 会导致框架对 Fragment 的自定义适配参数失去效果。所以如果您的 Fragment 允许屏幕旋转, 则请在 onCreateView 手动调用一次 AutoSize.autoConvertDensity(),如AutoSize.autoConvertDensity(getActivity(), 1080, true)。
如果您的 Fragment 不允许屏幕旋转, 则可以将下面调用 AutoSize.autoConvertDensity() 的代码删除掉public class CustomFragment1 extends Fragment implements CustomAdapt { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { //由于某些原因, 屏幕旋转后 Fragment 的重建, 会导致框架对 Fragment 的自定义适配参数失去效果 //所以如果您的 Fragment 允许屏幕旋转, 则请在 onCreateView 手动调用一次 AutoSize.autoConvertDensity() //如果您的 Fragment 不允许屏幕旋转, 则可以将下面调用 AutoSize.autoConvertDensity() 的代码删除掉 AutoSize.autoConvertDensity(getActivity(), 1080, true); return createTextView(inflater, "Fragment-1\nView width = 360dp\nTotal width = 1080dp", 0xffff0000); }
2. 主副单位的逐步替换
框架中同时支持主单位和副单位,对于对于旧项目中已使用dp或px的项目,可以通过逐步在新页面中使用主单位副单位。通过不断的迭代替换,最终将项目中的主单位如dp全替换为副单位px,或者将副单位px全替换为主单位dp。当单位都替换完成后,设置UnitsManager.setSupportDP(false)关闭对dp的支持,彻底隔离修改 density 所造成的不良影响。
或者都使用dp,不在支持副单位时设置UnitsManager.setSupportSubunits(Subunits.NONE)关闭对副单位的支持。3. 主副单位的同时支持
当使用者想将旧项目从主单位过渡到副单位, 或从副单位过渡到主单位时。因为在使用主单位时, 建议在 AndroidManifest 中填写设计图的 dp 尺寸, 比如 360 * 640。但在 AndroidManifest 中却只能填写一套设计图尺寸, 并且已经填写了主单位的设计图尺寸,所以当项目中同时存在副单位和主单位, 并且副单位的设计图尺寸与主单位的设计图尺寸不同时, 可以通过UnitsManager#setDesignSize() 方法配置。
如果副单位的设计图尺寸与主单位的设计图尺寸相同, 则不需要调用 UnitsManager#setDesignSize(), 框架会自动使用 AndroidManifest 中填写的设计图尺寸。
4. 自定义单位模拟器创建
布局时的实时预览在开发阶段是一个很重要的环节, 很多情况下 Android Studio 提供的默认预览设备并不能完全展示我们的设计图。所以我们就需要自己创建模拟设备, 大神@JessYan已经为我们准备好了dp、pt、in、mm 这四种单位的模拟设备创建方法,请点击查看链接https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md#preview
总结
经过我自己修改注释的源码在https://github.com/l424533553/MyAutoSize中,大家也可以自行封装框架,适合自己的才是最好的。
屏幕自适应的核心就是根据需要在使用之前不断修改density、scaledDensity、densityDpi达到适配效果。
相关链接:
扩展链接:
博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !
更多相关内容 -
android5.0屏幕录制
2015-09-17 18:14:265.0下,使用MediaProjection进行屏幕录制的demo。 我比较懒,里面只放了实现的java类和xml,工程请自行创建。 另外这个demo源自android sample,我基本上没有改动,所以它其实只能算是屏幕捕捉,就是把屏幕内容在一... -
C#调用屏幕键盘和触摸键盘
2014-08-06 22:00:26C# 调用 windows7 windows8 下的屏幕键盘 触摸键盘 文件中有两个类 一个是屏幕键盘类 一个触摸键盘类 代码里有详细的注释和说明 希望大家喜欢 -
Android屏幕适配
2022-02-28 16:54:53为什么Android就得做屏幕适配 由于Android系统是开源的,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,导致运行Android的设备多种多样,它们屏幕尺寸和像素密度都不甚相同。尽管我们通过基本的缩放...为什么Android就得做屏幕适配
由于Android系统是开源的,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,导致运行Android的设备多种多样,它们屏幕尺寸和像素密度都不甚相同。尽管我们通过基本的缩放和调整大小能使界面适配不同屏幕,但还是需要进一步优化,来确保所有界面能在不同设备上美观地展现出来。
怎么做屏幕适配
几个重要的概念
像素
像素是指由图像的小方格组成的,这些小方块都有一个明确的位置和被分配的色彩数值,小方格颜色和位置就决定该图像所呈现出来的样子。通俗来讲,小时候家里面大屁股电视没有信号的时候,肉眼可以看到一个一个点状的东西就是像素点。
屏幕尺寸
屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米
比如常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等
屏幕分辨率
屏幕分辨率是指在横纵向上的像素点数,单位是px,1px就是1个像素点。一般我们描述屏幕分辨率是以纵向像素*横向像素,比如1080x1920。表示宽度方向上有1080个像素点,长度方向上有1920个像素点。
单位: px ( pixel ), 1px=1 像素点Android 手机常见的分辨率: 320x480 、 480x800 、 720x1280 、 1080x1920UI设计师的设计图会以 px 作为统一的计量单位。屏幕像素密度
屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小,分辨率越高,像素密度越大。
假设设备内每英寸有 160 个像素,那么该设备的屏幕像素密度 就是160dpi。安卓手机对于每类手机屏幕大小都有一个相应的屏幕像素密度:
标准屏幕像素密度(mdpi): 每英寸长度上有160个像素点(160dpi),即称为标准屏幕像素密度(mdpi)。
我们可以通过两个adb命令获取到屏幕分辨率:
<1>adb shell wm size
<2>adb shell dumpsys window displays屏幕尺寸、分辨率、像素密度三者关系
一部手机的分辨率是宽x高,屏幕大小是以寸为单位,那么三者的关系是密度无关像素
density-independent pixel ,叫 dip 或 dp ,与终端上的实际物理像素点无关。可以保证在不同屏幕像素密度的设备上显示相同的效果。Android开发时用dp而不是px单位设置图片大小,是Android特有的单位。
场景:假如同样都是画一条长度是屏幕一半的线,如果使用 px 作为计量单位,那么 480x800 分辨率手机上设置应为240px ;在 320x480 的手机上应设置为 160px ,二者设置就不同了;如果使用 dp为单位,在这两种分辨率下, 160dp 都显示为屏幕一半的长度。在Android中,规定以160dpi(即屏幕分辨率为320x480)为基准:1dp=1px (谷歌给的标准)
dp 与 px 的转换:px = dp * (dpi / 160)逻辑密度:density = (dpi / 160)其实我们在布局文件中不管用的是dp还是sp还是px,最终都是以px为单位渲染到屏幕上的,具体可以看如下源码:
独立比例像素
sp,即scale-independent pixels,与dp类似,用来设置Android中文本显示大小,且可以根据文字大小首选项进行缩放。当文字尺寸是“正常”时1sp=1dp,当文字尺寸是“大”或“超大”时,1sp>1dp。类似我们在windows里调整字体尺寸 以后的效果——窗口大小不变,只有文字大小改变。
适配方案
几个重要的基本概念搞明白之后,我们再来看看屏幕适配方案。
布局组件适配
布局文件中使用wrap_content,match_parent,weight, dp, 0dp来控制视图组件的宽度和高度,避免使用px这种绝对尺寸。
按理来说,谷歌给我们开发者提供的dp和sp就已经可以做到屏幕适配了,为什么还会在某些手机上不能完全满足显示效果。其实我们通过px = dp * (dpi / 160) 这个公式就可以发现,如果手机厂商不按照谷歌的标准来,比如1080*1920的设备本来dpi应该是480,但是厂商设备的尺寸却是5英 寸,那么最终算出来的dpi大概就是440,那dpi都不一样了,dp当然就不一样呀。
布局适配(备用布局)
布局适配是指我们根据不同屏幕尺寸的设备设计不同的备用布局,以便应用在运行时根据屏幕的配置加载相应的UI布局。谷歌官方为我们提供了各种限定符。
size(尺寸)限定符(注:这种方式只适合
Android 3.2之前的版本
)res目录新建一个layout-large文件夹,布局名字和res/layout里面的同名。在平板电脑和电视的屏幕(>7英寸)上:实施 双面板 模式以同时显示更多内容,它会加载res/layout-large里面的布局,在手机较小的屏幕上:使用
单面板
模式分别显示内容,加载的是res/layout里面的同名布局。Smallest-Width(最小宽度)限定符
使用“最小宽度”屏幕尺寸限定符,我们可以为具有最小宽度(以密度无关像素 dp 或 dip 为度量单位)的屏幕提供备用布局。
通过将屏幕尺寸描述为密度无关像素的度量值,Android 允许我们创建专为非常具体的屏幕尺寸而设计的布局,同时让您不必对不同的像素密度有任何担心。
例如,您可以创建一个名为 main_activity 且针对手机和平板电脑进行了优化的布局,方法是在目录中创建该文件的不同版本,如下所示:
res/layout/main_activity.xml # For handsets (smaller than 600dp available width) res/layout-sw600dp/main_activity.xml # For 7” tablets (600dp wide and bigger)
最小宽度限定符指定屏幕两侧的最小尺寸,而不考虑设备当前的屏幕方向,因此这是一种指定布局可用的整体屏幕尺寸的简单方法。
下面是其他最小宽度值与典型屏幕尺寸的对应关系:
- 320dp:典型手机屏幕(240x320 ldpi、320x480 mdpi、480x800 hdpi 等)。
- 480dp:约为 5 英寸的大手机屏幕 (480x800 mdpi)。
- 600dp:7 英寸平板电脑 (600x1024 mdpi)。
- 720dp:10 英寸平板电脑(720x1280 mdpi、800x1280 mdpi 等)。
今日头条的适配方案
import android.app.Activity; import android.app.Application; import android.content.ComponentCallbacks; import android.content.res.Configuration; import android.util.DisplayMetrics; import androidx.annotation.NonNull; public class WindowUtils { /** * 系统的Density */ private static float sNonCompatDensity; /** * 系统的ScaledDensity */ private static float sNonCompatScaleDensity; public static void setCustomDensity(Activity activity) { Application application = activity.getApplication(); final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics(); if (sNonCompatDensity == 0) { // 系统的Density:长度相关参数(dp) sNonCompatDensity = appDisplayMetrics.density; // 系统的ScaledDensity:字体相关参数(sp) sNonCompatScaleDensity = appDisplayMetrics.scaledDensity; // 监听在系统设置中切换字体 application.registerComponentCallbacks(new ComponentCallbacks() { @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { if (newConfig != null && newConfig.fontScale > 0) { sNonCompatScaleDensity = application.getResources().getDisplayMetrics().scaledDensity; } } @Override public void onLowMemory() { } }); } /* * 如果UX提供的是480dp的设计稿,也就是分辨率为1080*1920的设计稿 * * 把系统的逻辑密度(density)和像素密度(dpi)根据UX提供的设计稿进行处理 */ final float targetDensity = appDisplayMetrics.widthPixels / 480; final float targetScaledDensity = targetDensity * (sNonCompatScaleDensity / sNonCompatDensity); final int targetDensityDpi = (int) (160 * targetDensity); /** * 设置Application对应的DisplayMetrics参数 */ appDisplayMetrics.density = targetDensity; appDisplayMetrics.scaledDensity = targetScaledDensity; appDisplayMetrics.densityDpi = targetDensityDpi; /** * 同时也要设置Activity对应的DisplayMetrics参数 */ final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics(); activityDisplayMetrics.density = targetDensity; activityDisplayMetrics.scaledDensity = targetScaledDensity; activityDisplayMetrics.densityDpi = targetDensityDpi; } }
约束布局
可以在Api9以上的Android系统使用ConstraintLayout(约束布局),它的出现主要是为了解决布局嵌套过多导致解析布局和绘制UI消耗时间过长的问题,从AS 2.3 起,官方的模板默认使 ConstraintLayout,也会自动帮我们添加约束布局的依赖。同时约束布局也可以解决90%以上的安卓屏幕适配问题。
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
约束布局基本属性
控制 View 最大尺寸和最小尺寸的属性,可以设置到 ConstraintLayout 上来控制其尺寸。
相对定位
与相对布局一样,约束布局中如果对控件没有添加约束时,默认控件会出现根布局的左上角。
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/color_7" android:gravity="center" android:text="没有约束" android:textSize="@dimen/text_size_32" app:layout_constraintWidth_min="@dimen/min_width_dip_120" tools:ignore="MissingConstraints" /> <TextView android:id="@+id/target" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintWidth_min="@dimen/min_width_dip_120" android:text="目标控件" android:gravity="center" android:textSize="@dimen/text_size_32" android:background="@color/color_10" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:background="@color/color_3" android:gravity="center" android:text="A" android:textSize="@dimen/text_size_32" app:layout_constraintEnd_toStartOf="@+id/target" app:layout_constraintTop_toTopOf="@+id/target" app:layout_constraintWidth_min="@dimen/min_width_dip_120" />
上面的代码效果如下:
首先目标控件是以父布局也就是根布局为基准,上下左右方向都与根布局建立约束,最终效果就是居中显示;而A控件又把目标控件作为基准,通过app:layout_constraintEnd_toStartOf="@+id/target"将其末端与目标控件始端建立约束;再通过app:layout_constraintTop_toTopOf="@+id/target"将其顶部与目标控件顶部建立约束。
关于相对定位的常用属性:
layout_constraintLeft_toLeftOf
layout_constraintLeft_toRightOf
layout_constraintRight_toLeftOf
layout_constraintRight_toRightOf
layout_constraintStart_toEndOf
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf
layout_constraintTop_toTopOf
layout_constraintTop_toBottomOf
layout_constraintBottom_toTopOf
layout_constraintBottom_toBottomOf
layout_constraintBaseline_toBaselineOfPS:start/end是为了适配比如阿拉伯国家阅读习惯的,阿拉伯国家是从右往左阅读的。
Baseline指的是文本基线,两个TextView的高度不一致,但是我们却希望它们文本对齐,这个时候就可以使用layout_constraintBaseline_toBaselineOf,代码如下:
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/color_10" android:gravity="center" android:text="文本对齐" android:textSize="@dimen/text_size_32" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBaseline_toBaselineOf="@+id/target" app:layout_constraintWidth_min="@dimen/min_width_dip_120" /> <TextView android:id="@+id/target" android:layout_width="@dimen/horizontal_min_dip_160" android:layout_height="@dimen/vertical_min_dip_160" android:background="@color/color_10" android:gravity="center" android:text="中间位置" android:textSize="@dimen/text_size_32" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintWidth_min="@dimen/min_width_dip_120" />
效果图:
未完待续...
参考文档:
《设备兼容性概览》https://developer.android.com/guide/practices/compatibility?hl=zh-cn#java -
2021年最全Android屏幕适配的度量单位px dp(dip) ppi dpi sp pt的区别(最详细,屏幕尺寸,屏幕分辨率,屏幕...
2021-04-03 16:32:121 Android屏幕适配相关概念 1.1 屏幕尺寸(Screen Size) 屏幕尺寸是屏幕的对角线的长度,单位是英寸,1英寸等于2.54厘米。比如常见的屏幕尺寸有3.5、4.0、5.5等。 1.2 屏幕分辨率(Screen Resolution) 屏幕分辨率是指...0 前言
快乐李同学最新在学习Android屏幕适配相关的知识点,其中涉及到了很多Android屏幕适配相关概念和度量单位,但是快乐李同学掌握的相关知识不牢固,于是决定写出这两篇文章对这方面的知识点做一个总结,Android屏幕适配系列文章共两篇:
1 Android屏幕适配相关概念
1.1 屏幕尺寸(Screen Size)
1.1.1 设备的屏幕尺寸
设备的屏幕尺寸是设备屏幕的对角线的长度,单位是英寸,1英寸等于2.54厘米。比如常见的设备屏幕尺寸有3.5"、4.0"、5.5"等。
1.1.2 应用的屏幕尺寸
应用的屏幕尺寸是Android系统为该应用所提供的可见空间。应用的屏幕尺寸并非设备的屏幕尺寸,而是综合考虑屏幕方向、系统装饰(如标题栏和导航栏)和窗口配置更改(例如当用户启用多窗口模式时)后的尺寸。
多窗口模式:Android7.0及以上设备支持的分屏模式、Android7.0及以上的大尺寸屏幕设备支持的自由窗口模式、Android8.0及以上设备支持的画中画模式。
例如以设备屏幕尺寸为1080px*2400px的Redmi K30Pro设备为例,我们把开发助手APP分别在不带导航栏的全屏模式、带导航栏的全屏模式、分屏模式和自由窗口模式下打开并检测屏幕属性,截图如下:
我们可以发现:这四种情况下检测出的屏幕分辨率永远都是1080px*2400px,这就是我们所说的设备的屏幕尺寸;但这四种情况下检测出的可用屏幕分辨率却各不相同,上图的蓝框圈中的区域便是可用屏幕分辨率所描述的区域,这也就是我们所说的应用的屏幕尺寸。1.2 屏幕分辨率(Screen Resolution)
屏幕分辨率是指在横向和纵向上的像素点数、单位是px(pixel),1px等于1个像素点,一般设备的屏幕分辨率以“纵向像素*横向像素”来表示,例如1920px*1080px。
1.3 度量单位px、dp(dip)、ppi、dpi、sp、pt
1.3.1 像素px(pixel)
像素,1px代表物理屏幕上面的一个像素点。由于Android设备分辨率繁杂、厂商过多的原因,px在实际的开发过程中不建议被使用。如宽高都为100px的图片,在不同分辨率的设备上可能显示的大小不一致,如下图(图片来自android_developer_screens_support):
1.3.2 密度无关像素dp/dip(Density-independent Pixels)
密度无关像素是一个基于屏幕物理密度的度量单位,在160dpi的屏幕中1dp大约等于1px。当在更高密度的屏幕上运行时,用于绘制1dp的像素数量会被一个适合屏幕dpi的density因子放大,例如在320dpi的屏幕中1dp大约等于2px。而在低密度屏幕上,1dp的像素数量会减少。
也就是说,dp与px的比值与屏幕物理密度成正相关,但不一定成正比。度量单位dp可以在布局中适当地调整UI组件的大小,以适合不同的屏幕密度。换句话说,它为您在不同设备上的UI元素的真实大小提供了一致性。
为什么说dp与px的比值与屏幕物理密度成正相关,而不是成正比呢?
这主要是因为Android开发者可以在代码中指定浮点数的dp或者px,但当在设备屏幕上按照对应的dp或px显示内容时要进行四舍五入取整,因为设备屏幕都是一个个像素点构成的,因此它们的关系是成正相关。
举个例子,一个宽高为11dp*11dp的ImageView,在density为1.0的设备屏幕上显示的实际像素为11px*11px,但在density为1.5的设备屏幕上显示的实际像素为17px*17px,而不是16.5px*16.5px。因为一个1px*1px的像素点只能是要么显示,要么不显示,所以Android系统中会将16.5px*16.5px四舍五入取整为17px*17px。
Android官方文档《支持不同的像素密度》对于屏幕适配的观点是:必须避免的第一个陷阱是使用像素px来定义距离或尺寸。使用像素来定义尺寸会带来问题,因为不同的屏幕具有不同的像素密度,所以同样数量的像素在不同的设备上可能对应于不同的物理尺寸。
例如下方同样是4.0英寸的两部手机,左边那台手机的分辨率很低,是320px*180px,右边那台是960px*540px。如果将显示字母a图片的ImageView宽高都设置为100px,那么左边手机显示的字母a图片很大,而右边手机显示的字母a图片很小。
只有将显示字母a图片的ImageView宽高都设置为100dp,才能出现下面的效果,即该字母a图片在两台分辨率不同的手机看起来实际的物理宽高差不多一致,而不是一大一小。
要在密度不同的屏幕上保持一个UI组件显示出相同的尺寸,您必须使用密度无关像素 (dp) 作为度量单位来设计界面。dp 是一个虚拟像素单位,1 dp 约等于在在基准密度160dpi屏幕上的1px。对于其他每个密度,Android 会将此值转换为相应的实际像素数。1.3.3 像素密度ppi(pixels per inch)
像素密度是一个表示打印图像或显示器单位面积上像素数量的指数。一般用来计量电脑显示器、电视机和手机屏幕的精细程度,例如手机销售商一般会标识出售手机的ppi,例如淘宝店中小米K30Pro手机标识的ppi为395。通常情况下,ppi越高的屏幕,屏幕显示的内容更细腻和真实,其中ppi的计算公式如下:
以分辨率1280*720、屏幕对角线尺寸为4.3英寸的设备为例:ppi = √(12802+7202) / 4.3 = 341.5359……≈342。
1.3.4 屏幕密度dpi(dots per inch)
屏幕密度是一个用于点阵数位影像的度量单位,意思是每一英寸长度中取样点或可显示点的数目。为简便起见,Android 将所有屏幕密度分组为六种通用密度,具体如下表:
类别 屏幕密度dpi 密度无关像素dp的缩放因子density ldpi ~120dpi 0.75 mdpi ~160dpi 1.0 hdpi ~240dpi 1.5 xhdpi ~320dpi 2.0 xxhdpi ~480dpi 3.0 xxxhdpi ~640dpi 4.0 1.3.4.1 密度无关像素dp的缩放因子density
在《Android官方文档-Reference-DisplayMetrics.density》中对屏幕密度dpi的缩放因子density有以下的解释:
density是密度无关像素dp的缩放因子,体现在
android.util
包中的DisplayMetrics.density
字段。将dp单位转换为px单位时,该字段是必须使用的缩放系数,160dpi屏幕的density是1,而240dpi屏幕的density是1.5,以此类推。利用density可以简单转换dp和px这两个度量单位:px = dp * density = dp * (dpi / 160)
假设在某一应用中,用户的手指至少移动16px之后,系统才会识别出滚动或滑动手势。在160dpi基准屏幕上,用户必须移动
16 pixels / 160 dpi
(等于一英寸的 1/10 或 2.5 毫米),系统才会识别该手势。而在配备高密度显示屏 (240dpi) 的设备上,用户的手指必须至少移动16 pixels / 240 dpi
,相当于 1 英寸的 1/15(1.7 毫米)。此距离短得多,因此用户会感觉应用在该设备上更灵敏。要解决此问题,必须在代码中以
dp
表示手势阈值,然后再转换为实际像素。例如://以dp为度量单位的手势阈值 private static final float GESTURE_THRESHOLD_DP = 16.0f; //获得当前设备的密度无关像素dp的缩放因子density final float density = getResources().getDisplayMetrics().density; //基于density将dp转化为当前设备适应的px, mGestureThreshold = (int) (GESTURE_THRESHOLD_DP * density + 0.5f) //使用以px为度量单位的mGestureThreshold变量值作为当前APP的手势阈值
DisplayMetrics.density
字段根据当前像素密度指定将dp
单位转换为像素时所必须使用的缩放系数。在中密度屏幕上,DisplayMetrics.density
等于 1.0;在高密度屏幕上,它等于 1.5;在超高密度屏幕上,等于 2.0;在低密度屏幕上,等于 0.75。此数字是一个系数,用其乘以dp
单位,即可得出当前屏幕的实际像素数。1.3.4.2 ppi与dpi的区别
dpi(dots per inch)、ppi(pixels per inch),前者突出dot(物理像素点),后者突出pixel(像素),dot是一个物理的像素点,pixel就不一定了,一个pixel可能由很多个物理dot组成。
PPI描述了数字图像的像素分辨率,而DPI描述了打印图像上的墨点数量。尽管PPI在很大程度上是指屏幕显示,但它也会影响设计的打印尺寸,从而影响输出的质量。而DPI与数字化无关,主要涉及印刷。
对Android而言,dpi等同于ppi,具体可以参考文章《PPI vs. DPI: what’s the difference?》。
1.3.5 缩放无关像素sp(Scale-independent Pixel)
在定义文本大小时,应该用可缩放像素sp作为单位。sp与dp很类似,但唯一的区别是:Android系统允许用户自定义文字尺寸大小(小、正常、大、超大等),当文字尺寸是“正常”时,1sp=1dp,而当文字尺寸是“大”或者“超大”时,1sp>1dp。
1.3.6 点pt(point)
pt在两种环境中有两种不同的含义:在PhotoShop中,pt用来指定电子图像的尺寸并映射到印刷设备;而在iOS应用开发中,pt用来将指定电子图像的尺寸并映射到iOS设备。
但在上述两种环境下,pt都是逻辑单位,用来隔离输出设备的精度差异,保证输出结果的物理尺寸不会忽大忽小。
1.3.6.1 iOS设备中的点pt(point)
在iOS应用开发中,pt用来将指定电子图像的尺寸并映射到iOS设备。在163ppi的iOS设备中,1pt映射成1px,且1inch(英尺)等于163pt。而对于其他不同ppi的iOS设备,1pt可能会映射成2px、3px等像素,具体可参考下述表格:
1.3.6.2 PhotoShop中的点pt(point)
在PhotoShop中,pt用来指定电子图像的尺寸并映射到印刷设备。其中PhotoShop指定,当ppi为72时,PhotoShop中1pt映射成1px。且1inch等于72pt。
2 2021年最详细的Android屏幕适配方案汇总
请阅读快乐李同学写的文章《2021年最详细的Android屏幕适配方案汇总》。
本文参考文献:
Android开发者-文档-指南-设备兼容性-设备兼容性概览
Android开发者-文档-指南-设备兼容性-支持不同的屏幕尺寸
Android开发者-文档-指南-设备兼容性-支持不同的像素密度
【Android屏幕适配】浅析px、dp、ppi、dpi、sp
-
Android从屏幕底部弹出PopupWindow
2015-09-03 09:55:20Android从屏幕底部滑动弹出PopupWindow,有动画效果,类似于sharesdk的分享页面滑动弹出效果。 -
Flutter 屏幕适配
2020-09-15 18:18:57志当存高远。——诸葛亮 屏幕尺寸大全 菜单栏共有5个选项,包括手机、平板、手表、电脑、显示器,分别显示屏幕尺寸、PPI、纵横比、dp和px 单位下的宽✘...屏幕分辨率是指屏幕图像的精密度,是显示器所能显示的...志当存高远。——诸葛亮
屏幕尺寸大全
菜单栏共有5个选项,包括手机、平板、手表、电脑、显示器,分别显示屏幕尺寸、PPI、纵横比、dp和px 单位下的宽✘高,以及DPI。
官方设计规范
适配原理
屏幕尺寸
严格来说,屏幕尺寸实际被物理尺寸和显示分辨率两个部分定义。而我们今天对各类手机、Pad 设备所说的屏幕尺寸,只指物理尺寸。如果一块手机屏幕的物理尺寸是 5.0 英寸,即是指该手机屏幕对角线的长度。
屏幕分辨率
屏幕分辨率是指屏幕图像的精密度,是显示器所能显示的像素的具体数值。 如一个手机标称分辨率是 400 x 800,即表示屏幕横向由 400 个像素点组成,纵向由 800 个像素点组成。由于屏幕上的点、线和面都是由像素组成的,屏幕具备的像素点越多,画面就越精细。分辨率越高,单位面积内显示的信息就越多,我们能看到的内容就越多。
屏幕比例
屏幕比例是指屏幕的宽度与高度的比例,今天更多指分辨率的比例,即
屏幕比例 = 屏幕横向分辨率 / 屏幕纵向分辨率
此外,在各类设备、平台之间也有一些比较常用的比例,同时也推荐大家在开发对应设置或平台的应用时使用推荐比例。
使用推荐比例可以让用户在使用我们的 App 时更舒适,带来更好的使用体验。DPI
DPI(Dots Per Inch),每英寸所拥有的点数,原用于打印机、鼠标的精确度指标。
在屏幕方面一般使用 PPI 来表示精度。PPI
PPI(Pixels Per Inch),每英寸所拥有的像素数,屏幕的 PPI 越高,表示屏幕中的每个像素点之间的距离越接近,像素的密度越高,这样屏幕内容看起来就更加细腻、真实。
而当 PPI 超过 300 时,屏幕被认为达到了视网膜级别,一般情况下人眼已经较难察觉 300 以上 PPI 之间的差别。屏幕密度(Density)
Density 由 Android 1.6 版本(Android API Level 4)为了适配不同大小的屏幕而提出,表示每英寸有多少个显示点(逻辑值),它的单位是 DPI。
在 Android 原生开发中,常常使用 dp/dip/sp 等单位来定义视图、文字的宽高
理论上当 Density 的值为 160 DPI 时,1px = 1dp,当前屏幕的 Density 为 320 DPI 时,2px = 1dp倍率DPR(devicePixelRatio)
有的地方也叫设备像素比,是设备像素dp和设备独立像素dips的比例,
也就是dpr = dp / dips逻辑分辨率
逻辑分辨率在 APICloud 应用中也可以被当做显示分辨率使用。
逻辑分辨率与屏幕分辨率在当今的主流设备中是不相同的,公式为:
逻辑分辨率 = 屏幕分辨率 / 屏幕倍率
在 Android 系统中,根据 DP 的定义 1dp = 1px 时, Density 为 160,可知公式为:
Android 屏幕倍率 = Density / 160
如 iPhone 4 的屏幕分辨率为 640 x 960,逻辑分辨率为:
640 / 2 x 960 / 2 = 320 x 480
而小米 2 的屏幕分辨率为 720 x 1280,Density 为 320,逻辑分辨率为:
720 / (320 / 160) x 1280 / (320 / 160) = 360 x 640屏幕适配(自适应)方案
现代手机屏幕尺寸各不相同,导致我们平时写布局的时候会在个不同的移动设备上显示的效果不同。为了达到一套代码所有手机体验一致效果,需要做尺寸上的适配。
计算公式:实际尺寸 = UI尺寸 * 设备宽度/设计图宽度
1px方案 : 1px = 1 / 设备像素比
750设计图为例
import 'package:flutter/material.dart'; import 'dart:ui'; class Adapt { static MediaQueryData mediaQuery = MediaQueryData.fromWindow(window); static double _width = mediaQuery.size.width; static double _height = mediaQuery.size.height; static double _topbarH = mediaQuery.padding.top; static double _botbarH = mediaQuery.padding.bottom; static double _pixelRatio = mediaQuery.devicePixelRatio; static var _ratio; static init(int number){ int uiwidth = number is int ? number : 750; _ratio = _width / uiwidth; } static px(number){ if(!(_ratio is double || _ratio is int)){Adapt.init(750);} return number * _ratio; } static onepx(){ return 1/_pixelRatio; } static screenW(){ return _width; } static screenH(){ return _height; } static padTopH(){ return _topbarH; } static padBotH(){ return _botbarH; } }
// 设置文本大小 30 为设计图尺寸 new Text( 'Hello World!', style: TextStyle( fontSize: Adapt.px(30), ) )
// 容器尺寸大小设置 一个设计图为 300*300像素的容器 new Container( width: Adapt.px(300), height: Adapt.px(300), )
// 1px new Container( decoration: new BoxDecoration( border: new Border(bottom:BorderSide(width: Adapt.one())), ), )
屏幕适配方案插件-完美解决屏幕适配
插件地址
添加依赖
dependencies: flutter: sdk: flutter # 添加依赖 flutter_screenutil: ^0.5.0
属性 类型 默认值 描述 width int 1080px 设计稿中设备的宽度,单位px height int 1920px 设计稿中设备的高度,单位px allowFontScaling bool false 设置字体大小是否根据系统的“字体大小”辅助选项来进行缩放 初始化
@override Widget build(BuildContext context) { ScreenUtil.init(context, width: 750, height: 1624, allowFontScaling: true); }
//填入设计稿中设备的屏幕尺寸 //默认 width : 1080px , height:1920px , allowFontScaling:false ScreenUtil.instance = ScreenUtil.getInstance()..init(context); //假如设计稿是按iPhone6的尺寸设计的(iPhone6 750*1334) ScreenUtil.instance = ScreenUtil(width: 750, height: 1334)..init(context); //设置字体大小根据系统的“字体大小”辅助选项来进行缩放,默认为false : 字体随着系统的“字体大小”辅助选项来进行缩放 ScreenUtil.instance = ScreenUtil(width: 750, height: 1334, allowFontScaling: true)..init(context);
适配尺寸
//长方形: Container( width: ScreenUtil.getInstance().setWidth(375), height: ScreenUtil.getInstance().setHeight(200), ), //如果你想显示一个正方形: Container( width: ScreenUtil.getInstance().setWidth(300), height: ScreenUtil.getInstance().setWidth(300), ),
适配字体-传入设计稿的px尺寸
//传入字体大小,默认不根据系统的“字体大小”辅助选项来进行缩放(可在初始化ScreenUtil时设置allowFontScaling) ScreenUtil.getInstance().setSp(28) //传入字体大小,根据系统的“字体大小”辅助选项来进行缩放(如果某个地方不遵循全局的allowFontScaling设置) ScreenUtil(allowFontScaling: true).setSp(28) //for example: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text('我的文字大小在设计稿上是25px,不会随着系统的文字缩放比例变化', style: TextStyle( color: Colors.black, fontSize: ScreenUtil.getInstance().setSp(24))), Text('我的文字大小在设计稿上是25px,会随着系统的文字缩放比例变化', style: TextStyle( color: Colors.black, fontSize: ScreenUtil(allowFontScaling: true).setSp(24))), ], )
其他相关api
ScreenUtil.pixelRatio //设备的像素密度 ScreenUtil.screenWidth //设备宽度 ScreenUtil.screenHeight //设备高度 ScreenUtil.bottomBarHeight //底部安全区距离,适用于全面屏下面有按键的 ScreenUtil.statusBarHeight //状态栏高度 刘海屏会更高 单位px ScreenUtil.textScaleFactory //系统字体缩放比例 ScreenUtil.getInstance().scaleWidth // 实际宽度的dp与设计稿px的比例 ScreenUtil.getInstance().scaleHeight // 实际高度的dp与设计稿px的比例
@override Widget build(BuildContext context) { //设置适配尺寸 (填入设计稿中设备的屏幕尺寸) 假如设计稿是按iPhone6的尺寸设计的(iPhone6 750*1334) ScreenUtil.instance = ScreenUtil(width: 750, height: 1334)..init(context); print('设备宽度:${ScreenUtil.screenWidth}'); //Device width print('设备高度:${ScreenUtil.screenHeight}'); //Device height print('设备的像素密度:${ScreenUtil.pixelRatio}'); //Device pixel density print( '底部安全区距离:${ScreenUtil.bottomBarHeight}'); //Bottom safe zone distance,suitable for buttons with full screen print( '状态栏高度:${ScreenUtil.statusBarHeight}px'); //Status bar height , Notch will be higher Unit px print('实际宽度的dp与设计稿px的比例:${ScreenUtil.getInstance().scaleWidth}'); print('实际高度的dp与设计稿px的比例:${ScreenUtil.getInstance().scaleHeight}'); print( '宽度和字体相对于设计稿放大的比例:${ScreenUtil.getInstance().scaleWidth * ScreenUtil.pixelRatio}'); print( '高度相对于设计稿放大的比例:${ScreenUtil.getInstance().scaleHeight * ScreenUtil.pixelRatio}'); print('系统的字体缩放比例:${ScreenUtil.textScaleFactory}'); return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Row( children: <Widget>[ Container( width: ScreenUtil.getInstance().setWidth(375), height: ScreenUtil.getInstance().setHeight(200), color: Colors.red, child: Text( '我的宽度:${ScreenUtil.getInstance().setWidth(375)}dp', style: TextStyle( color: Colors.white, fontSize: ScreenUtil.getInstance().setSp(12), ), ), ), Container( width: ScreenUtil.getInstance().setWidth(375), height: ScreenUtil.getInstance().setHeight(200), color: Colors.blue, child: Text('我的宽度:${ScreenUtil.getInstance().setWidth(375)}dp', style: TextStyle( color: Colors.white, fontSize: ScreenUtil.getInstance().setSp(12), )), ), ], ), Text('设备宽度:${ScreenUtil.screenWidth}px'), Text('设备高度:${ScreenUtil.screenHeight}px'), Text('设备的像素密度:${ScreenUtil.pixelRatio}'), Text('底部安全区距离:${ScreenUtil.bottomBarHeight}px'), Text('状态栏高度:${ScreenUtil.statusBarHeight}px'), Text( '实际高度的dp与设计稿px的比例:${ScreenUtil.getInstance().scaleHeight}', textAlign: TextAlign.center, ), Text( '实际高度的dp与设计稿px的比例:${ScreenUtil.getInstance().scaleHeight}', textAlign: TextAlign.center, ), Text( '宽度和字体相对于设计稿放大的比例:${ScreenUtil.getInstance().scaleWidth * ScreenUtil.pixelRatio}', textAlign: TextAlign.center, ), Text( '高度相对于设计稿放大的比例:${ScreenUtil.getInstance().scaleHeight * ScreenUtil.pixelRatio}', textAlign: TextAlign.center, ), SizedBox( height: ScreenUtil.getInstance().setHeight(100), ), Text('系统的字体缩放比例:${ScreenUtil.textScaleFactory}'), Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text('我的文字大小在设计稿上是25px,不会随着系统的文字缩放比例变化', style: TextStyle( color: Colors.black, fontSize: ScreenUtil.getInstance().setSp(24))), Text('我的文字大小在设计稿上是25px,会随着系统的文字缩放比例变化', style: TextStyle( color: Colors.black, fontSize: ScreenUtil(allowFontScaling: true).setSp(24))), ], ) ], ), ), ); }
全面屏、折叠屏适配与兼容问题
一 启动白屏问题
采用Flutter 开发的app,无论Android还是Ios,都会出现白屏的现象,大概持续1-3秒,他会根据手机或模拟器的速溶而不同,时间可长可短。Flutter 应用在启动的时候会先启动Flutter SDK, 然后加载到内存里面 ,然后完成渲染,在这个过程中 它是没有内容可以显示的,因此会显示白屏.注意 :Android App启动的时候,会有一个默认的白屏 ,这个白屏在启动的时候会显示主题,如果主体色不是透明的 就会有个白屏
Android的白屏非两个部分,一个是主题的白屏(如果主题不是透明的时候,点击图标会显示白屏),二,当App启动起来之后,会显示启动屏(如果没有启动屏,会显示默认的白屏)
二 Flutter 全面屏适配指南
1)全面屏特点,以及存在问题特点:1 大屏占比高、长宽比不再试16:9,达到了19.5:9甚至更高2 短边的像素,density的取值是一样的 所有适配的是长边问题:
1. 传统布局的高度不足,导致上下留黑边2. 基于屏幕顶部或底部的布局,如弹框,在全面屏手机上会发生位移3. 安全区域问题Flutter中全面屏的页面适配分两种情况:1.一种是对于页面已经使用了Scaffold的appBar 与bottomNavigationBar页面是不需要额外适配的,因为Scaffold框架会自动化帮助我们完成这些适配工作;2. 另外一种情况,没有使用了Scaffold或者Scaffold的appBar 与bottomNavigationBar页面,改如何适配全面屏呢?(1) 适配要点. 顶部NavigationBar上部留安全区域. 底部NavigationBar底部留安全区域对于安全区域的适配有两种方案:1. 采用 SafeArea来包裹页面,SafeArea是Flutter中的一个用于全面屏的一个组件,类似RN中SafeAreaView 主要用于解决适配全面屏手机的安全取悦问题;2.借助MeadiaQuery.of(context).padding 获取屏幕四周的padding,然后根据padding自己手动实现对安全区域的控制;. 方案一:相对简单的,只需要引入SafeArea,但不够灵活. 方案二:需要借助MeadiaQuery.of(context).padding自己实现对安全区域的控制,相对复杂些,但灵活度高借助 MediaQuery.of(context).padding手动适配
提示:使用MediaQuery.of时要留意它所属的widget 不能直接和runApp解除,需要一个带有MaterialApp的widget 包裹一下这样才能使用MediaQuery.of
富有表现力,漂亮的用户界面
-
Qt写的Windows下屏幕录制程序源码
2014-05-30 21:52:45Qt + ffmpeg写的Windows屏幕录制程序(包含源程序和编译好的可执行程序) 程序使用方法: ScreenCapture w h w和h分别表示希望录制的帧宽度和高度 -
OLED屏幕和LCD屏幕的区别与优劣
2019-11-11 17:40:47随着科技越来越多的手机用上了OLED屏幕,大家也开始逐渐了解起来了手机的屏幕,这篇文章呢将会用最简单的语言和图片,告诉你OLED屏幕和LCD屏幕究竟有什么差别。 要知道OLED屏幕 和 LCD屏幕的区别,我们需要从运行... -
2021年最详细的Android屏幕适配方案汇总
2021-04-09 19:55:271 Android屏幕适配的度量单位和相关概念 建议在阅读本文章之前,可以先阅读快乐李同学写的文章《Android屏幕适配的度量单位和相关概念》,这篇文章包含了阅读本文的一些基础知识,推荐阅读。 2 Android屏幕适配的... -
Android电脑源码通过adb实现实时屏幕演示
2014-04-24 12:25:24Android电脑源码通过adb实现实时屏幕演示 -
教您电脑屏幕闪烁与屏幕抖动怎么办
2021-07-29 07:54:29最近看到一些朋友在贴吧上说,自己有的时候家里的电脑屏幕会突然抖动起来或者一直在闪烁,这样严重的影响到了电脑的使用,而且很多人都不知道遇见这种情况应该怎么办。那么在这里小编就来和大家说一说,以后遇见了... -
电脑屏幕尺寸怎么计算?-常见电脑屏幕尺寸的计算方式 - 河东软件园
2021-07-27 03:58:04电脑屏幕尺寸如何计算?电脑屏幕尺寸的计算方式!现如今电脑屏幕尺寸有多种多样,我们通过对笔记本与台式显示器的尺寸进行对比就会发现明显的区别,市面上常见的电脑屏幕尺寸包含19、21、21.5、22、23、25、27、32、... -
css适应屏幕 CSS背景图片自适应屏幕大小
2021-06-09 11:08:06怎么屏幕自适应????? 我全用了margin的百分比但依旧达不到自适应的/* 打印样式 */ @media print { color: red; } /* 手机等小屏幕手持设备 */ @media screen and (min-width: 320px) and (max-width: 480px) { ... -
笔记本可以当屏幕用 笔记本屏幕当显示器用
2021-07-31 00:36:23怎样可以把笔记本电脑的屏幕当显示器用?笔记本一般都是LCD屏,其数据是通过几对差分信号传送的,该差分信号是从北桥拉出来的。所以要把笔记本作为显示器,必须通过差分线向屏传输信号,只能通过北桥。所以要把屏... -
realme gt neo3 看屏幕是天马屏幕还是京东方(图文教程)
2022-04-20 11:41:08realme gt neo3 看屏幕是天马屏幕还是京东方(图文教程) realme gt neo3 看屏幕是天马屏幕还是京东方(图文教程) 由于官网删除我的贴子,所以我发布再csdn上,第一次使用,多多包含! 教程准备 1:电脑 2:数据线 3:... -
ABAP SCREEN 选择屏幕相关用法,屏幕假必输,屏幕检查等
2020-01-05 16:23:17一, 屏幕基本元素(单值输入框,RANGE输入,单选按钮,复选框定义) TABLES: SFLIGHT. SELECTION-SCREEN BEGIN OF BLOCKbk1WITH FRAME TITLE text-001. PARAMETERS:P_CARRIDLIKESFLIGHT-CARRID ." 单值输入框 ... -
android屏幕录制
2021-11-29 20:32:56Android低版本需要实现屏幕录制,据我目前了解到的,都是需要获取Root权限才行。 本文介绍的屏幕录制是居于Android5.0及之后Android API实现的屏幕录制,以及录制后编码成h264格式保存到文件。屏幕采集主要使用类... -
Android 屏幕适配方案
2015-05-04 13:08:471、概述大家在Android开发时,肯定会觉得屏幕适配是个尤其痛苦的事,各种屏幕尺寸适配起来蛋疼无比。如果我们换个角度我们看下这个问题,不知道大家有没有了解过web前端开发,或者说大家对于网页都不陌生吧,其实... -
电脑如何扩展屏幕_给电脑扩展屏幕的设置方法
2021-07-27 05:24:48正常情况下,一台电脑就只有显示一个屏幕桌面,工作中经常要显示多个屏幕,这样可以一边工作,一边做其他事情。那么如何给电脑扩展屏幕?相信很多小伙伴还不懂怎么操作?别担心,下面给大家说一下给电脑扩展屏幕的... -
Android自适应屏幕大小和布局
2021-03-07 15:11:56一:不同的layoutAndroid手机屏幕大小不一,有480x320, 640x360, 800x480,854x480.怎样才能让App自动适应不同的屏幕呢? 其实很简单,只需要在res目录下创建不同的layout文件夹,比如layout-640x360,layout-800x480,... -
怎么解决电脑屏幕模糊、屏幕显示不清晰
2021-07-30 02:18:45有的时候,我们重装了系统之后电脑屏幕会变得模糊,不清晰,图片文字有颗粒感。屏幕整体感觉变差,那么这究竟是怎么回事呢?为什么会导致这样的现象呢?我们应该怎么解决让屏幕变得清晰呢?一起来看看吧。分辨率调节... -
电脑屏幕总是闪烁怎么解决
2021-07-30 03:17:37最近不知怎么回事,显示器屏幕总是出现闪烁或是电脑屏幕抖动,所以到网上查了一下开机却出现电脑屏幕闪烁,电脑屏幕抖动!看屏幕上的字很费眼,所以建议大家遇到电脑屏幕闪烁或者电脑屏幕抖动需要及时去解决电脑故障。... -
c++访问屏幕输出
2022-04-03 12:26:391、c++访问屏幕输出 2、C++获取桌面,显示器指定区域内的显示内容 3、C++访问视频流输出 4、C++ get the video output from special screen area 5、C++ Live screen capture realtime 6、How to capture the ... -
电脑屏幕显示泛白怎么修正
2021-07-27 02:24:08显示器使用久了难免会出现故障,那当屏幕显示泛白了,怎么办呢?下面是学习啦小编为大家介绍电脑屏幕显示泛白的修正方法,欢迎大家阅读。电脑屏幕显示泛白的修正方法1、打开控制面板,单击颜色管理,弹出颜色设置... -
《手机屏幕》参数与选择
2019-08-20 14:00:17一、手机屏幕:又称手机显示触摸屏,是手机的核心部件之一。手机屏幕是和终端用户交互的设备元器件,是手机成本的主要组成部分。一部手机性能的优劣和价格的高低,在很大程度上体现在手机屏幕上。 二、手机屏幕... -
多种电脑屏幕关闭方法推荐
2021-06-23 11:37:59有时因为需要节约电脑电量,有时因为为避免同事窥屏,有时由于顾及后台运行任务进程诸如听歌、电脑磁盘碎片整理等多种原因,这些均促使我们需要关闭电脑屏幕。无论基于何种原因促使我们关闭电脑屏幕,总的来说其并不... -
电脑屏幕出现:图像设定自动设定,屏幕优化中请稍等...怎么解决?
2021-06-27 09:04:43而电脑也是会出现问题的,也会出现许多小毛病困扰着我们,虽说有的可以简单的使用断电来解决,但不是所有的毛病都能如此,不知道大家有没有遇见过:电脑在正常开机使用情况下,屏幕出现:图像设定自动设定,屏幕优化... -
屏幕适配工具
2014-11-11 11:55:13Android中进行屏幕适配的小工具,很实用的 -
Android模拟点击屏幕,实现按键精灵点击屏幕功能
2019-02-27 15:19:48我在实现安卓模拟点击屏幕固定位置时尝试了很多方法,碰了不少壁,现在我将我实现的方法分享给大家,以及我尝试过的方法也分享给大家,让大家在开发的路上少走些弯路。 首先我尝试用安卓辅助功能Accessibility...