vivo 互联网客户端团队- Xu Jie
日益新增的机型,给开发人员带来了很多的适配工作。代码能不能对立、apk能不能对立、物料如何选取、款式怎么展现等等都是困扰开发人员的问题,本计划就是介绍不同机型的共线计划,打消开发人员的疑虑。
一、日益纷纷的机型带来的挑战
1.1 背景
科技是提高的,人们对美的要求也是逐步晋升的,所以才有了当初市面上不拘一格的机型
(1)比方vivo X60手机采纳纤薄曲面屏设计,属于直板机型。
(2)比方vivo 折叠屏高端手机,提供更优质的视觉体验,属于折叠屏机型。
(3)比方vivo pad,领有优良的操作手感和高级的质感,属于平板机型。
1.2 咱们的挑战
在此之前,咱们次要是为直板手机去服务,咱们的开发只有适配这种支流的直板机器,咱们的UI次要去设计这种直板手机的效果图,咱们的产品和经营次要为这种直板机型去抉择物料。
可是随着这种不拘一格机型的呈现,那么问题就来了:
(1)开发人员的适配老本高了,是不是针对每一种机型,都要做个独自的利用进行适配呢?
(2)UI设计师要做的效果图要多了,是不是要针对每种机型都要设计一套效果图呢?
(3)产品和经营须要抉择的物料更受限制了,会不会这个物料在一个机器上失常。在其余机器上就不失常了呢?
为什么这么说,上面以开发者的角度来做介绍,把咱们面临的问题,做阐明。
二、 开发者的困境
2.1 全机型适配老本太高
日渐丰盛的机型适配让咱们这些android开发人员疲于奔命,尽管能够依照要求进行适配,然而大屏幕的机型适配老本仍然比拟高,因为这些机型不同于传统的直板手机的宽高比例(9:16)。所以有的利用罗唆就间接两边留白,内容区域展现在屏幕正地方,这种成果,当然很差。
案例1:某个视频APP页面,未做pad上的适配,关上之后的成果如下,两边大量留白,是不可操作的区域。
案例2:某新闻资讯类APP,在pad上的适配成果如下,可见的范畴内,信息流展现内容较少,图片有拉伸、含糊的问题。
2.2 全机型适配老本高在哪
下面的案例其实只是外表的问题之一,作为开发人员,须要思考的因素有很多,首先要想到这些机型有什么特点:
而后才是须要解决的问题:
三、寻找全机型适配计划之旅
3.1 计划探讨与确定
页面拉伸、左右留白是景象,这也是用户的间接体验。那么这就是咱们要改善的中央,所以当初就有方向了,围绕着 “如何在可见区域内,展现更多的信息” 。这不是布局的简略从新排列组合,因为 计划相对不是只有开发决定如何实现就能够怎么实现的,一个apk承载着性能到用户手里波及了多方角色的染指。产品经理须要整顿需要、经营人员须要配置物料、公布apk,测试须要测试等等,所以最终的计划不是一方定下来的,而是一个协调对立后的后果。
既然要去探讨计划,那么就要有根据,在此省略探讨、评审、定稿的过程。
先来看看直板、折叠屏、pad的内部轮廓图,晓得页面状态如何。
3.2 计划落地示意图
每个利用要展现的内容不统一,然而原理统一,此处就以上面几个款式为根底介绍原理。准则也比较简单,尽可能展现更多内容,不要呈现大面积的空白区域。
上面没有介绍分栏模式的适配,因为分栏的模式也可能被用户敞开,最终成为全屏模式,所以说,能够抉择只适配全屏模式,这样的适配老本较低。当然,这个也要依据本人模块的状况来确定,比方微信,更适宜左右屏的分栏模式。
3.2.1 直板机型适配计划骨骼图
直板机型,目前支流的机型,宽高比根本是9:16,能够最大限度地展现比拟多的内容,比方下图中的模块1、模块2、 模块3的图片。
3.2.2 折叠屏机型适配计划骨骼图
折叠屏机型,屏幕可旋转,然而宽高比根本是1:1,高度和直板机器根本差不多,能够达到2000px的像素,所以在纵向上,也能够最大限度地展现比拟多的内容,比方下图中的模块1、模块2、 模块3的图片。
3.2.3 PAD机型适配计划骨骼图
pad平板,屏幕可旋转,并且旋转后的宽高比差别较大,纵向时,宽高比是5 : 8,横向时,宽高比是8 : 5。
在pad纵向时,其实高度像素是足够展现很多内容的,比方下图中的模块1、模块2、 模块3的图片;
然而在pad横向时,没方法展现更多的内容(倒是有个计划,最初再说),只能下图中的模块1、模块2的图片。
3.3 计划落地标准
3.3.1 一套代码适配所有机型
确定一个apk能不能适配所有机型,首先要解决的是要合乎不同机型的个性,比方直板手机只能纵向显示,折叠屏和pad反对横竖屏旋转。
形容如下:
(1)需要
- 直板屏:强制固定竖屏;
- 折叠屏:外屏固定竖屏、内屏(大屏)反对横竖屏切换;
- PAD端:反对横竖屏切换;
咱们须要在以上三端通过一套代码实现下面的需要。
(2)横竖屏切换
有以下2种办法:
- 形式1)
通过在AndroidManifest.xml中设置:android:screenOrientation属性
a) android:screenOrientation="portrait"强制竖屏;
b) android:screenOrientation="landscape"强制横屏;
c) android:screenOrientation="unspecified"默认值,能够横竖屏切换;
形式2)
在代码中设置:activity.setRequestedOrientation(**);
a) setRequestedOrientation(ActivityInfo.SCREEN\_ORIENTATION\_PORTRAIT); 设置竖屏;
b)setRequestedOrientation(ActivityInfo.SCREEN\_ORIENTATION\_LANDSCAPE); 设置横屏;
c)setRequestedOrientation(ActivityInfo.SCREEN\_ORIENTATION\_UNSPECIFIED); 能够横竖屏切换;
(3)不同设施反对不同的屏幕横竖屏形式
1)直板屏:
因为是强制竖屏,所以,能够通过在AndroidManifest.xml中给Activity设置android:screenOrientation="portrait"。
2)折叠屏:
外屏与直板屏是保持一致的,暂且不探讨。然而内屏(大屏)要反对横竖屏切换。如果是一套代码,显然是无奈通过AndroidManifest文件来实现的。这里其实零碎框架曾经帮咱们实现了对应内屏时横竖屏的逻辑。总结就是,折叠屏能够与直板屏保持一致,在AndroidManifest.xml中给Activity设置android:screenOrientation="portrait",如果切换到内屏时,零碎主动疏忽掉screenOrientation属性值,强行反对横竖屏切换。
3)PAD端:
当然了,并不是所有的我的项目对应的零碎都会主动帮咱们疏忽screenOrientation属性值,这时候就须要咱们本人来实现了。
咱们通过在Activity的基类中设置setRequestedOrientation(ActivityInfo.SCREEN\_ORIENTATION\_UNSPECIFIED),发现的确可能使以后页面横竖屏自在切换了。然而在启动activity的时候遇到了问题。当咱们从横屏状态A界面启动一个acitivity的B界面时,发现B界面先是竖屏,而后切换到了横屏(如图1所示)。再试了屡次仍旧如此,肉眼可见的切换过程显然不能满足咱们的需要。这阐明通过java代码动静调整横竖屏的技术方向是行不通的。综上所述,通过同一份代码无奈满足PAD端和直板屏的互斥的需要。
那还有没有其余形式呢。别忘了,咱们Android打包全流程是通过gradle实现的,咱们是不是能够通过切面编程的思维,针对不同的设施打出不同的包。
计划确定了,在此进行技术验证。
gradle编译其中一个重要环节就是对依赖的aar、本地module中的AndroidManifest文件进行merge,最终输入一份长期的残缺清单文件,寄存在/app/build/intermediates/merged_manifest/*Release/门路下。
因而,咱们能够在AndroidManifest文件merge实现之后对该临时文件中的android:screenOrientation字段值信息进行动静批改,批改实现之后再存回去。这样针对pad端就能够独自打出一份apk文件。
外围代码如下:
//pad反对横竖屏def processManifestTask = project.tasks.getByName("processDefaultNewSignPadReleaseManifest");if (processManifestTask != null) { processManifestTask.doLast { pmt -> def manifestPath = pmt.getMultiApkManifestOutputDirectory().get().toString() + "/AndroidManifest.xml" if (new File(manifestPath).exists()) { String manifest = file(manifestPath).getText() manifest = manifest.replaceAll("android:screenOrientation=\"portrait\"", "android:screenOrientation=\"unspecified\""); file(manifestPath).write(manifest) println(" =============================================================== manifestPath: " + manifestPath) } }}
(4)apk的数量
到这里为止,java代码是完全一致,没有辨别的,要害就在于框架有没有提供出疏忽screenOrientation的能力,如果提供了,咱们只须要输入一个apk,就能适配所有机型,
如果没有这个能力,咱们就须要应用gradle打出额定的一个apk,满足可旋转的要求。
3.3.2 一套物料配所有机型
1、等比放大物料
通过下面的落地计划的要求,对于模块2的图片,展现成果是不一样的,如下图:
(1)直板手机下面,模块2的图片1在下面,图片2、3散布于左下角和右下角
(2)折叠屏或者pad下面,模块2的图片1在右边,图片2、3散布于右侧
(3)折叠屏和pad上的模块2的图片,绝对于直板手机来说,做了款式的调整,高低的款式改为了左右。图片也做了对应的放大,保障横向上能够填充整个屏幕的宽度。
(4)为了形象地示意解决后的成果,看下上面的示意图即可。
2、高度不变,裁剪物料
对于模块3的图片,能够回顾3.2中的展现款式,要求是
(1)直板手机下面,模块3中图片1的高度此处为300px。
(2)折叠屏或者pad下面,模块3的图片1的高度也是300px,然而内容不能缩小。
(3)解决方案就是提供一张原始大图,如果规格为2400px*300px,在直板手机上左右进行裁剪,如下图所示。折叠屏和pad下面间接进行展现。而裁剪这一步,放在服务端进行,因为客户端做裁剪,比拟耗时。
(4)为了形象地示意解决后的成果,看下上面的示意图即可。
3.3.4 无感刷新
无感刷新,次要是体现在折叠屏的内外屏切换,pad的横竖屏旋转这些场景,如何保障页面不会呈现切换、旋转时候的闪现呢?
(1)这就要提前准备好数据源,保障在页面变动时,立刻notify。
(2)咱们的页面列表最好应用recyclerview,因为recyclerview反对部分刷新。
(3)数据源驱动UI,千万不要在UI层面判断机型做UI的动静计算,页面会闪屏,体验不好。
3.4 计划落地实战
下面介绍了不同机型的适配标准,这个没有疑难之后,间接通过案例来看下具体如何施行。
如上图所示,选购页能够大抵分为 分类导航栏区域 和 内容区域,其中内容区域是由多个楼层组成。
3.4.1 UI如何设计的
如图所示,可能直观地感触到,从直板手机到折叠屏内屏再到Pad横屏,当设施的可显示面积增大时,页面充分利用空间展现更多的商品信息。
3.4.2 不同设施的辨别形式
通过后面的简略介绍,对选购页的整体布局及不同设施上的UI展现有所理解,上面来看下如何在多个设施上实现一套代码的适配。
首先第一步,要如何辨别不同的设施。
在辨别不同的设施前,先看下可能从设施中取得哪些信息?
1)分辨率
2)机型
3)以后屏幕的横、竖状态
先说论断:
- 直板手机:通过分辨率来辨别
- 折叠屏:通过机型和内外屏状态来辨别
- Pad:通过机型和以后屏幕的横、竖状态来辨别
所以这里依据这几个特点,提供一个工具。
不同设施的辨别形式。
/** * @function 判断以后手机的屏幕是处于哪个屏幕类型:目前三个屏幕范畴:别离为 <= 528dp、528 ~ 696dp、> 696dp,对应的别离是失常直板手机、折叠屏手机内屏和Pad竖屏、和Pad横屏 */public class ScreenTypeUtil { public static final int NORMAL_SCREEN_MAX_WIDTH_RESOLUTION = 1584; // 失常直板手机:屏幕最大宽度分辨率;Pad的分辨率(1600*2560), 1584 = 528 * 3, 528dp是UI在精选页标注的直板手机范畴 public static final int MIDDLE_SCREEN_MAX_WIDTH_RESOLUTION = 2088; // 折叠屏手机:屏幕最大宽度分辨率(1916*1964, 旋转:1808*2072),2088 = 696 * 3, 2088dp是UI在精选页标注的折叠屏开展范畴 public static final int LARGE_SCREEN_MAX_WIDTH_RESOLUTION = 2560; // 大屏幕设施:屏幕宽度暂定为 Pad的高度 public static final int NORMAL_SCREEN = 0; // 失常直版手机屏幕 public static final int MIDDLE_SCREEN = 1; // 折叠屏手机内屏开展、Pad竖屏 public static final int LARGE_SCREEN = 2; // Pad横屏 public static int getScreenType() { Configuration configuration = BaseApplication.getApplication().getResources().getConfiguration(); return getScreenType(configuration); } // 留神这里的newConfig 在Activity、Fragment、View 中的onConfigurationChanged中取得的newConfig传入,如果取得不了该值,能够应用getScreenType()办法 public static int getScreenType(@NonNull Configuration newConfig) { // Pad 通过机型标记位及以后处于横竖屏状态 来判断以后屏幕类型 if (SystemInfoUtils.isPadDevice()) { return newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? LARGE_SCREEN : MIDDLE_SCREEN; } // Fold折叠屏 通过机型标记及内外屏状态 来判断以后屏幕类型 if (SystemInfoUtils.isFoldableDevice()) { return SystemInfoUtils.isInnerScreen(newConfig) ? MIDDLE_SCREEN : NORMAL_SCREEN; } // 一般手机 通过分辨率判断 return AppInfoUtils.getScreenWidth() <= NORMAL_SCREEN_MAX_WIDTH_RESOLUTION ? NORMAL_SCREEN : (AppInfoUtils.getScreenWidth() <= MIDDLE_SCREEN_MAX_WIDTH_RESOLUTION ? MIDDLE_SCREEN : LARGE_SCREEN); }}
3.4.3 实现计划
(1)数据源驱动UI扭转的思维
对于直板手机来说,选购页只有一种状态,放弃竖屏展现。
对于折叠屏来说,折叠屏能够由内屏切换到外屏,也就波及到了两种不同状态的切换。
对于Pad来说,Pad反对横竖屏切换,所以也是两种不同状态切换。
当屏幕类型、横竖屏切换、内外屏切换时,Activity\Fragment\View 会调用onConfigurationChanged办法,因而针对直板手机、折叠屏及Pad能够将数据源的切换放在此处。
无论是哪种设施,最多是只有两种不同的状态,因而,数据源这里能够筹备两套:一种是Normal、一种是Width,对直板手机而言:因为只有一种竖屏状态,因而只须要一套数据源即可;对折叠屏而言:Normal寄存的是折叠屏外屏数据源,Width寄存的是折叠屏内屏数据源;对Pad而言:Normal寄存的是Pad竖屏状态数据源,Width寄存的是Pad横屏状态数据源。
(2)内容区域
右侧的内容区域是一个Fragment,在这个Fragment外面蕴含了一个RecyclerView。
每个子楼层
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root_classify_horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <xxx.widget.HeaderAndFooterRecyclerView android:id="@+id/shop_product_multi_rv" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
每个楼层也是一个独自的RecyclerView,以楼层4为例,楼层4的每一行商品都是一个RecyclerView,每个RecyclerView应用GridLayoutManager来管制布局的展示列数。
(3)数据源
以折叠屏为例:针对每个子楼层的数据,在解析时,就先筹备两套数据源:一种是Normal、一种是Width。
在申请网络数据回来后,在解析数据实现后,寄存两套数据源。这两套数据源要依据UI设计的规定来组装,例如以折叠屏的楼层4为例:
折叠屏-外屏-楼层4:一行展现2个商品信息。
折叠屏-内屏-楼层4:一行展现3个商品信息。
留神:这里的2、3数字是UI设计之初就定下来的,每行商品都是一个RecyclerView,并且应用GridLayoutManager来管制其列数,因而这个2、3也是传入到GridLayoutManager的列数值,这里要保持一致。
子楼层的数据源解析
//这里的normalProductMultiClassifyUiBeanList汇合中寄存了2个商品信息for (ProductMultiClassifyUiBean productMultiClassifyUiBean : normalProductMultiClassifyUiBeanList) { productMultiClassifyUiBean.setFirstFloor(isFirstFloor); shopListDataWrapper.addNormalBaseUiBeans(productMultiClassifyUiBean);}//这里的normalProductMultiClassifyUiBeanList汇合中寄存了3个商品信息for (ProductMultiClassifyUiBean productMultiClassifyUiBean : widthProductMultiClassifyUiBeanList) { productMultiClassifyUiBean.setFirstFloor(isFirstFloor); shopListDataWrapper.addWidthBaseUiBeans(productMultiClassifyUiBean);}
因而,到这里就曾经获取了所需的数据源局部
(4)屏幕类型切换
还是以折叠屏为例,折叠屏外屏切换到内屏,此时Fragment会走onConfigurationChanged办法。
屏幕类型切换-数据源切换-更新RecyclerView。
public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); //1、 首先进行内容区域中的RecyclerViewAdapter、数据源判空 if (mRecyclerViewAdapter == null || mPageBeanAll == null) { return; } //2、判断以后的屏幕类型,留神:这个中央是调用3提供的办法:ScreenTypeUtil.getScreenType(newConfig) // 直板手机、折叠屏外屏 if (ScreenTypeUtil.NORMAL_SCREEN == ScreenTypeUtil.getScreenType(newConfig)) { mPageBeanAll.setBaseUiBeans(mPageBeanAll.getNormalBaseUiBeans()); } else if (ScreenTypeUtil.MIDDLE_SCREEN == ScreenTypeUtil.getScreenType(newConfig)) { if (SystemInfoUtils.isPadDevice()) { // Pad的竖屏 mPageBeanAll.setBaseUiBeans(mPageBeanAll.getNormalBaseUiBeans()); } else { // 折叠屏的内屏 mPageBeanAll.setBaseUiBeans(mPageBeanAll.getWidthBaseUiBeans()); } } else { // Pad的横屏、大分辨率屏幕 mPageBeanAll.setBaseUiBeans(mPageBeanAll.getWidthBaseUiBeans()); } //获取以后屏幕类型的最新数据源 mRecyclerViewAdapter.setDataSource(mPageBeanAll.getBaseUiBeans()); //数据源驱动楼层UI扭转 mRecyclerViewAdapter.notifyDataSetChanged();}
通过onConfigurationChanged办法,可能看到数据源是如何依据不同屏幕类型进行切换的,当数据源切换后,会通过notifyDataSetChanged办法来扭转UI。
四、至简之路的铸就
大道至简,遵循标准和准则,就能够想到如何对多机型进行适配,别陷入细节。
以这个作为指导思想,能够做很多其余的适配。上面做些列举,但不解说实现形式了。
1、文字显示区域放大
如下图所示,题目的长度,在整个容器显示宽度变宽的同时,也跟着一起变动,保障内容的长度能够自适应的变动。
2、弹框款式的兼容
如下图所示,蓝色区域是键盘的高度,在屏幕进行旋转的时候,键盘的高度也是变动的,此时可能会呈现遮挡住本来展现的内容,此处的解决形式是:让内容区域能够高低滑动。
3、摄像头地位的解决
如下图所示,在屏幕旋转之后,摄像头能够呈现在右下角,此时如果不对页面进行设置,那么就可能呈现内容区域无奈占据整个屏幕区域的问题,体验比拟差,此处的解决形式是:设置页面沉迷式,摄像头能够正当地笼罩一部分内容。
五、咱们解脱困扰了吗
5.1 解决原先的问题
通过后面的介绍,咱们晓得了,vivo官网的团队针对折叠屏和pad这种大屏,采取了全屏展现的计划,一开始的时候,咱们遇到的问题也失去了解决:
(1)开发人员的适配老本高了,是不是针对每一种机型,都要做个独自的利用进行适配呢?
Answer:依照全屏模式的设计方案,折叠屏和pad也就是一种大尺寸的机器,开发人员判断机型的分辨率和尺寸,抉择一种对应的布局展现就好了,只用一个利用就能搞定。
(2)UI设计师要做的效果图要多了,是不是要针对每种机型都要设计一套效果图呢?
Answer:制订一套标准,大于某个尺寸时,展现其余款式,所有信息内容都依照这种标准来,不会呈现设计凌乱的状况。
(3)产品和经营须要抉择的物料更受限制了,会不会这个物料在一个机器上失常。在其余机器上就不失常了呢?
Answer:以不变应万变,应用一套物料,适配不同的机型曾经能够落地了,不必再放心在不同的机器上展现不对立的问题。
5.2 咱们还能够做什么
5.2.1 咱们的长处
折叠屏和pad两款机器,曾经在市面上应用较长时间,各家厂商也纷纷采取了不同的适配计划来晋升交互体验,然而往往存在上面几个问题:
1、针对不同机型,采纳了不同的安装包。
这种计划,其实会减少保护老本,前期的开发要基于多个安装包去开发,更加耗时。
2、适配了不同的机型,然而在一些场景下的款式不现实。
比方有些APP做了分栏的适配,然而没有做全屏的适配,成果就比拟差,这里可能也是思考到了投入产出比。
3、目前的适配领导文档对于开发人员来说指导性较弱。
各种适配领导文档,还是比拟偏差于官网,对于开发人员来说,还是无奈提前辨认问题,遇到问题还是要理论去解决,https://developer.huawei.com/consumer/cn/doc/90101
基于此,咱们的长处如下:
1、咱们只有一个安装包。
咱们是一个安装包适配所有机型,每种机型的APP展现的款式尽管不同,对于开发者来说,就是减少了一个款式,思路比拟清晰。
2、全场景适配。
不同机型的纵向、横竖屏切换,都做到了完满适配,一套物料适配所有机型也是咱们的一个特色。
3、有针对性地提供适配计划。
本计划是基于理论开发遇到的问题,进行的梳理,能够帮忙开发人员解决理论可能遇到的问题,具备更好的参考性。
5.2.2 咱们还有什么要改良
回首计划,咱们这里做到的是应用全屏模式去适配不同机型,更多的实用于像京东、淘宝、商城等电商类APP上,实际上,当初有些非APP会采纳分栏的模式做适配,这也是一种跟用户交互的形式,本计划没有提到分栏,后续分栏落地后,对这部分会再进行补充。