关于ui:vivo官网APP全机型UI适配方案

5次阅读

共计 10990 个字符,预计需要花费 28 分钟才能阅读完成。

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

正文完
 0