简单高效无侵入式Android屏幕适配

最近在学习网易的Android课程,网易的老师提供了网易云音乐的屏幕适配解决方案,主要有两种,17年前是采用自定义缩放布局,17年后是采用的是工具类发方案,现在这两种方案在网易云音乐中是同时存在的。互不影响。在对比dimen适配、density适配、百分比布局适配等各种适配方案之后,网易云音乐的这两种方案在我们的项目中都是非常简洁高效的。

屏幕适配的相关概念
像素(px)

通常所说的像素,就是CCD/CMOS上光电感应元件的数量,一个感光元件经过感光,光电信号转换,A/D转换等步骤以后,在输出的照片上就形成一个点,我们如果把影像放大数倍,会发现这些连续色调其实是由许多色彩相近的小方点所组成,这些小方点就是构成影像的最小单位“像素”(Pixel)。

分辨率

手机在横向、纵向上的像素点数总和,一般描述成宽高 ,即横向像素点个数乘以纵向像素点个数。

屏幕尺寸(inch)

手机对角线的物理尺寸,单位 英寸(inch),一英寸大约2.54cm,常见的尺寸有4.7寸、5寸、5.5寸、6寸。

屏幕像素密度(dpi)

每英寸长度上像素点个数。

例如每英寸内有160个像素点,则其像素密度为160dpi。

公式: 像素密度=像素/尺寸 (dpi=px/in)

标准屏幕像素密度(mdpi)

每英寸长度上还有160个像素点,即称为标准屏幕像素密度(mdpi)。

像素密度等级

手机真实像素密度与标准屏幕像素密度(160dpi)的比值。官方给出的0.75、1、1.5、2、3、4,即对应120dpi、160dpi、240dpi、320dpi、480dpi、640dpi

密度无关像素(dp)

density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。可以保证在不同屏幕像素密度的设备上显示相同的效果,是安卓特有的长度单位。

独立比例像素(sp)

scale-independent pixel,叫sp或sip,字体大小专用单位,可根据字体大小首选项进行缩放;

推荐使用12sp、14sp、18sp、22sp作为字体大小,不推荐使用奇数和小数,容易造成精度丢失,12sp以下字体太小。

尺寸、像素、像素密度关系

几种屏幕适配方案的对比
dimen适配

适配交由系统根据手机分辨率自动读取不同的配置来完成,开发者无需手动处理任何细节。但坏处也很明显,由于对于各种分辨率,为了保证能最大精度的适配,我们要写一大堆的dimen文件,当然,直接用工具生成即可,主要问题是增大了安装包的大小。

density适配

通过动态修改手机的density来实现,但这种方式的缺陷是,有些厂商的手机不允许修改density的操作,再者,修改density一般是对宽度进行适配,而高度的适配则需要单独处理,否则有可能出现垂直方向显示不全的问题,这跟设备的屏幕比例有关,虽然可以通过加个ScrollView解决,但总体来说还是比较繁琐,效果也只是差强人意。

百分比布局适配

谷歌官方有提供了这套解决方案,在GitHub上可以找到,并且还有开发者对其进行了进一步封装完善。由于UI小姐姐给我们的效果图一般都是直接用像素(px)做单位来设计和标注的,所以如果要使用百分比布局,那对于大部分的元素我们都需要手动计算其横纵占比,这无疑增加了UI和开发之间沟通和实现成本。另外,我们所有的布局文件中用到的RelativeLayout、FrameLayout、LinearLayout等这些都必须替换成对应的百分比布局容器,对于自定义View也不太友好。

网易云音乐的两种适配方式
1.自定义缩放布局

这种方式是通过继承布局控件,重写onMeasure()方法,在此方法中通过当前屏幕分辨率与设计分辨率的横纵缩放比,对子控件的width、height、padding、margin等属性进行相应的缩放。以RelativeLayout为例:

public class UIRelativeLayout  extends RelativeLayout {private boolean flag=true;public static final float STANDARD_WIDTH=1080f;public static final float STANDARD_HEIGHT=1920f;public UIRelativeLayout(Context context) {super(context);}public UIRelativeLayout(Context context, AttributeSet attrs) {super(context, attrs);}public UIRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (flag) {flag = false;float scaleX = getHorizontalScaleValue();float scaleY = getVerticalScaleValue();int childCount = this.getChildCount();for (int i = 0; i < childCount; i++) {View child = this.getChildAt(i);LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();layoutParams.width=(int) (layoutParams.width * scaleX);layoutParams.height = (int) (layoutParams.height * scaleY);layoutParams.leftMargin = (int) (layoutParams.leftMargin * scaleX);layoutParams.rightMargin = (int) (layoutParams.rightMargin * scaleX);layoutParams.topMargin = (int) (layoutParams.topMargin * scaleY);layoutParams.bottomMargin = (int) (layoutParams.bottomMargin * scaleY);}}}public float getHorizontalScaleValue(){return  ((float)(displayMetricsWidth)) / STANDARD_WIDTH;}public float getVerticalScaleValue(){return ((float)(displayMetricsHeight))/(STANDARD_HEIGHT-systemBarHeight);}
}

自定义好布局文件之后,我们只需要在xml中是用我们自定义的这个类即可。这里需要注意的是,子控件的所有属性值必须使用像素作为单位。

2.UI工具类

这种方案其实是将自定义缩放布局的onMeasure()中的计算部分单独封装,不再交由布局来处理,而是封装成单独的工具类,对外提供各种布局的适配接口。

工具类UIUtils.java

public class UIUtils {private static UIUtils instance;//标准宽高   以UI图为准public static final float STANDARD_WIDTH = 1080f;public static final float STANDARD_HEIGHT = 1920f;public static float displayMetricsWidth;public static float displayMetricsHeight;public static float systemBarHeight;public static UIUtils getInstance(Context context){if (instance == null){instance = new UIUtils(context);}return instance;}public static UIUtils getInstance(){if (instance == null){throw new RuntimeException("UiUtil应该先调用含有构造方法进行初始化");}return instance;}private UIUtils(Context context){//计算缩放系数WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics displayMetrics = new DisplayMetrics();if (displayMetricsWidth == 0.0f || displayMetricsHeight == 0.0f){//获取设备的真实宽高windowManager.getDefaultDisplay().getMetrics(displayMetrics);systemBarHeight = getSystemBarHeight(context);//横屏if (displayMetrics.widthPixels > displayMetrics.heightPixels){displayMetricsWidth = (float)(displayMetrics.heightPixels);displayMetricsHeight = (float)(displayMetrics.widthPixels-systemBarHeight);}else {//竖屏displayMetricsWidth = (float)(displayMetrics.widthPixels);displayMetricsHeight = (float)(displayMetrics.heightPixels-systemBarHeight);}}}/*** 计算状态栏高度* @param context context* @return 高度*/private int getSystemBarHeight(Context context){return getValue(context,"com.android.internal.R$dimen","system_bar_height",48);}private int getValue(Context context, String dimeClass, String system_bar_height, int defaultValue) {
//        com.android.internal.R$dimen    system_bar_height   状态栏的高度try {Class<?> clz=Class.forName(dimeClass);Object object = clz.newInstance();Field field=clz.getField(system_bar_height);int id=Integer.parseInt(field.get(object).toString());return context.getResources().getDimensionPixelSize(id);} catch (Exception e) {e.printStackTrace();}return defaultValue;}/*** 获取适配后的宽度* 例如传入100宽度,单位为像素,计算其在当前设备上应显示的像素宽度*/public int getWidth(int width) {return Math.round((float)width * displayMetricsWidth / STANDARD_WIDTH);}/*** 获取适配后的高度*/public int getHeight(int height) {return Math.round((float)height * displayMetricsHeight / (STANDARD_HEIGHT-systemBarHeight));}}

在这里我们需要把状态栏高度考虑进去,这一点对于刘海屏适配是很重要,如果需要,还可以将底部的虚拟按键高度也考虑进去。

上述工具类只是完成了缩放系数相关的计算工作,我们还需要一个工具来来为各种布局提供适配接口。

各种布局接口类ViewCalculateUtil.java

public class ViewCalculateUtil {/*** RelativeLayout* 根据屏幕的大小设置view的高度,间距*/public static void setViewLayoutParam(View view, int width, int height, int topMargin, int bottomMargin, int lefMargin, int rightMargin){RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams();if (layoutParams != null){if (width != RelativeLayout.LayoutParams.MATCH_PARENT && width != RelativeLayout.LayoutParams.WRAP_CONTENT && width != RelativeLayout.LayoutParams.FILL_PARENT){layoutParams.width = UIUtils.getInstance().getWidth(width);}else{layoutParams.width = width;}if (height != RelativeLayout.LayoutParams.MATCH_PARENT && height != RelativeLayout.LayoutParams.WRAP_CONTENT && height != RelativeLayout.LayoutParams.FILL_PARENT){layoutParams.height = UIUtils.getInstance( ).getHeight(height);}else{layoutParams.height = height;}layoutParams.topMargin = UIUtils.getInstance( ).getHeight(topMargin);layoutParams.bottomMargin = UIUtils.getInstance( ).getHeight(bottomMargin);layoutParams.leftMargin = UIUtils.getInstance( ).getWidth(lefMargin);layoutParams.rightMargin = UIUtils.getInstance( ).getWidth(rightMargin);view.setLayoutParams(layoutParams);}}/*** 设置LinearLayout中 view的高度宽度** @param view* @param width* @param height*/public static void setViewLinearLayoutParam(View view, int width, int height, int topMargin, int bottomMargin, int lefMargin,int rightMargin){LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();if (width != RelativeLayout.LayoutParams.MATCH_PARENT && width != RelativeLayout.LayoutParams.WRAP_CONTENT && width != RelativeLayout.LayoutParams.FILL_PARENT){layoutParams.width = UIUtils.getInstance( ).getWidth(width);}else{layoutParams.width = width;}if (height != RelativeLayout.LayoutParams.MATCH_PARENT && height != RelativeLayout.LayoutParams.WRAP_CONTENT && height != RelativeLayout.LayoutParams.FILL_PARENT){layoutParams.height = UIUtils.getInstance( ).getHeight(height);}else{layoutParams.height = height;}layoutParams.topMargin = UIUtils.getInstance( ).getHeight(topMargin);layoutParams.bottomMargin = UIUtils.getInstance( ).getHeight(bottomMargin);layoutParams.leftMargin = UIUtils.getInstance( ).getWidth(lefMargin);layoutParams.rightMargin = UIUtils.getInstance( ).getWidth(rightMargin);view.setLayoutParams(layoutParams);}public static void setTextSize(TextView view, int size){view.setTextSize(TypedValue.COMPLEX_UNIT_PX,             UIUtils.getInstance().getHeight(size));}}

在这里我只封装了RelativeLayoutLinearLayout,其他的布局可以自行添加。

工具类的使用

        UIUtils.getInstance(this.getApplicationContext());setContentView(R.layout.activity_main);tvText3 = findViewById(R.id.tvText3);tvText4 = findViewById(R.id.tvText4);ViewCalculateUtil.setViewLinearLayoutParam(tvText3, 540, 100, 0, 0, 0, 0);ViewCalculateUtil.setViewLinearLayoutParam(tvText4, 1080, 100, 0, 0, 0, 0);ViewCalculateUtil.setTextSize(tvText3,30);
总结

适配的方法和原理,无非就是缩放。这两种方式的核心也是缩放。 采用这两种方式, 大到电视机,小到智能手表,我们都可以方便地将手机布局与UI设计图保持一致。无论是在手表还是手机还是电视上,我们看到的app页面都是一样的效果。

Android屏幕适配(网易云音乐方案)相关推荐

  1. Android高仿网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

    简介 这是一个使用Java(以后还会推出Kotlin版本)语言,从0开发一个Android平台,接近企业级的项目(我的云音乐),包含了基础内容,高级内容,项目封装,项目重构等知识:主要是使用系统功能, ...

  2. android+仿最新网易云音乐底面栏,安卓仿网易云音乐通知栏控制音乐,默认显示Notification bigView...

    最近在做一个音乐播放器的时候遇到了一个关于notification的问题,在网上找了很久都没有头绪.后来找到了解决的办法,特意记录一下. 问题描述 首先请看网易云音乐的通知栏 普通高度的notific ...

  3. 音乐歌单Android,LitePager(仿网易云音乐-歌单广场效果)

    LitePager,一个轻量级的ViewPager,仿新版网易云歌单广场 使用方式: 添加依赖: implementation 'com.wuyr:litepager:1.0.0' APIs: Met ...

  4. Android 屏幕适配的一种方案

    转载请标明出处: http://blog.csdn.net/xuehuayous/article/details/51671937 本文出自:[Kevin.zhou的博客] 前言:在<Andro ...

  5. Android高仿网易云音乐播放界面

    现在很多的播放器的播放界面都是采用光盘的转动,下面是我仿造网易的播放界面.先上两张图: 第一张为播放前的界面,第二张为点击播放按钮的图片.布局文件如下: <RelativeLayout xmln ...

  6. 3.Android高仿网易云音乐-首页复杂发现界面布局和功能

    0.效果图 效果图依次为发现界面顶部,包含首页轮播图,水平滚动的按钮,推荐歌单:然后是发现界面推荐单曲,点击单曲就是直接进入播放界面:最后是全局播放控制条上点击播放列表按钮显示的播放列表弹窗. 1.整 ...

  7. 1.Android高仿网易云音乐-启动界面实现和动态权限处理

    0.效果 效果图依次为启动界面,第一次显示用户协议对话框,动态获取权限. 系列文章目录导航 目录 1.实现分析 启动基本上没有什么难点,但在真实项目逻辑还是比较多:就是布局,然后显示用户协议对话框,动 ...

  8. 2.Android高仿网易云音乐-引导界面和广告界面实现

    效果图 效果图依次为图片广告,视频广告,引导界面. 系列文章目录导航 目录 1.实现分析 广告界面就是显示图片和视频,所以可以放一个图片控件,视频控件,然后跳过按钮,提示按钮,WiFi预加载提示都是放 ...

  9. Android高级-网易云音乐屏幕适配

    为什么要进行屏幕适配 屏幕碎片化表现为以下几个方面: 1:屏幕尺寸碎片化: 2:屏幕密度碎片化 3:厂商碎片化:水滴屏,刘海屏 屏幕适配常见方式 二 谷歌推出的百分比布局 手写百分比布局; 为什么百分 ...

最新文章

  1. 阿里云西安ACE同城会 | 钉钉生态应用促进企业信息化实战沙龙
  2. Cypress 基础 - 元素的定位
  3. kafka集群 kubernetes_为什么 Kubernetes 如此受欢迎?
  4. Tomcat 8.5 配置 SSL 证书 1
  5. #苹果maccmsv10# redis memcached 缓存的若干问题解决
  6. NEC电影服务器型号,NEC数字电影放映一体机NC2300S-A+详细信息_产品参数_价格_联系方式_DAV数字音视工程网...
  7. input file multiple 配合springmvc实现多文件上传
  8. Win10/Server2016镜像集成离线补丁
  9. Web基础配置篇(九): 抓包工具的介绍、安装及基本使用
  10. 2020-03-31
  11. 机器学习笔记--模型评估之一:准确率与召回率,平均根误差(RMSE、平均绝对百分比误差(MAPE)
  12. 汽车电子嵌入式相关知识
  13. AI读书笔记:《智能简史(谁会替代人类成为主导物种)》
  14. 【微信公众平台开发之一】微信公众平台开发环境搭建
  15. uniapp+canvas实现app在线电子签名
  16. 微信公众号开发-使用微信网页授权进行登录并加上过滤器判断是否已登录
  17. 常见的无线路由器的基本设置步骤
  18. Geospark电火花使用再记录
  19. uVision, MDK, realview的关系
  20. dubbo的简单搭建

热门文章

  1. 2022年国家高新技术企业认定评审最新标准及补贴政策重点,补贴10-50万
  2. idea双击无反应。打不开解决办法
  3. 已解决error: Microsoft Visual C++ 14.0 or greater is required. Get it with “Microsoft C++ Build Tools“:
  4. 【计算机网络】(二)网络技术与应用
  5. 【NLP】余弦定理计算文本相似度
  6. 阿里云大学-虚拟化技术入门-听课笔记
  7. 数学的三大核心领域——几何学范畴
  8. 安信实验室呼吁键盘厂商申请windows徽标认证(WHQL)
  9. 世界十大经典汽车赛道盘点
  10. Windows7 任务栏功能的开发