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会采纳分栏的模式做适配,这也是一种跟用户交互的形式,本计划没有提到分栏,后续分栏落地后,对这部分会再进行补充。