一、背景介绍
随着 Android 10 与 iOS 13 先后支持暗黑模式,优酷客户端在完成了架构统一和组件标准化的工作之后,分发场景的复杂业务可以基于统一的技术方案进行暗黑模式的适配。
二、业务介绍
由于优酷业务分发场景业务复杂,承载页面众多(二十多种),在落地暗黑模式前,我们对优酷分发场景的业务进行了梳理,大致可以分为三类:
第一类是固定入口的页面,比如首页、频道、Feed 流、二级页,它们承接了分发场景的大部分业务,拥有样式丰富的组件库,它们的共性非常高。
第二类是独立业务方开发的二级页,这些二级页由垂直业务团队维护。由于优酷客户端已完成架构统一和组件标准化的工作,各个业务团队适配暗黑模式都是基于同一套技术方案,适配就变成了一个标准化的工作。业务团队只需按统一的方式适配即可,极大提高了开发效率。
第三类是一些历史遗留的一些老页面,这些页面没有固定的入口,入口分散在各个页面和各个组件, 适配暗黑模式成本相对较高。
这样的页面可以有两种处理方式:
流量小,非核心的分发场景,面临下线的业务不进行暗黑模式的适配。
有业务价值的页面进行迁移或合并,统一用新版的二级页承载。
三、暗黑适配
页面展现分几种状态:转场态, 加载态(Loading), 展现态,异常态。暗黑模式适配的主要工作是适配页面的展现态;转场态和加载态的生命周期虽然很短,但是不能忽略,否则很容易出现亮色-暗色页面状态的切换,会破坏整体的沉浸式浏览体验,异常态也是因为类似原因不能忽略。
1、页面级别
1) 顶部/底部导航/容器
顶部导航支持换背景色;底部导航支持换图片资源、文字颜色、背景颜色;容器支持背景色。
Android 示例代码
//获取下拉刷新DesignToken色值
int refresBgColor = ColorConfigureManager.getInstance().getColorMap().get(YKN_DEEP_BLUE_GRADIENT_MIDDLE_POINT);
//设置下拉刷新
mYkClassicsHeader.setBgColor(refresBgColor);
//获取顶部导航背景资源,对应android来说都是一个命名,暗黑模式的资源单独放在night目录下
int defaultImage = R.drawable.yk_top_bg;
//设置顶部导航背景
setPlaceHoldForeground(getResources().getDrawable(defaultImage));
//获取页面背景色
int backGroundColor=ColorConfigureManager.getInstance().getColorMap().get(YKN_PRIMARY_BACKGROUND);
//设置页面背景色
setFragmentBackGroundColor();
复制代码
iOS 示例代码
/// 暗黑变化回调
/// @param manager 当前的主题管理对象
/// @param identifier 当前主题的标志
/// @param theme 当前主题对象
- (void)ykn_themeDidChangeByManager:(YKNThemeManager *)manager identifier:(__kindof NSObject<NSCopying> *)identifier theme:(__kindof NSObject *)theme {
[super ykn_themeDidChangeByManager:manager identifier:identifier theme:theme];
// 顶部渐变色:深蓝渐变顶部Token
CGColorRef topCGColor = UIColor.ykn_deepBlueGradientTopPoint.CGColor;
// 底部渐变色:深蓝渐变底部Token
CGColorRef midCGColor = UIColor.ykn_deepBlueGradientMiddlePoint.CGColor;
if (topCGColor && midCGColor) {
_gradientLayer.colors = @[(__bridge id)topCGColor,
(__bridge id)midCGColor];
}
}
复制代码
2) 页面/卡片/图片打底色
这里是比较容易遗漏的,Loading 态,错误态,打底图都需要适配,否则页面容易出现“白块”影响整体暗黑效果。
Android 示例代码
//从颜色管理器中取出背景色 YKN_PRIMARY_BACKGROUND 为 Design Token定义的色值
int bgColor=ColorConfigureManager.getInstance().getColorMap().get(YKN_PRIMARY_BACKGROUND)
setFragmentBackGroundColor(bgColor);
复制代码
加载骨架图通过 Android 系统的资源寻址自动获得。
我们只需建立暗黑模式的资源目录(night),并把骨架图的 drawble 放进去即可。
iOS 示例代码
// Feed流Loading图通过动态资源方式获取
itemView.image = [UIImage ykn_home_feed_loading_default];
/// 首页Feed流默认Loading图
+ (UIImage *)ykn_home_feed_loading_default {
return [UIImage ykn_imageWithThemeProvider:^UIImage * _Nonnull(__kindof YKNThemeManager * _Nonnull manager, NSString * _Nullable identifier, NSObject<YKNThemeProtocol> * _Nullable theme) {
// 暗黑模式下的图
if ([identifier isEqualToString:YKNThemeIdentifierDark]) {
return [UIImage imageNamed:@"home_feed_loading_default_d"] ;
}
// 浅色模式下的图
return [UIImage imageNamed:@"home_feed_loading_default"];
}];
}
复制代码
3) 暗黑与氛围/换肤的兼容问题
优酷分发场景支持换肤、氛围和暗黑模式,优先级为氛围>换肤>暗黑。所有页面、组件都在此规则上进行适配。
a) 页面同时存在氛围+暗黑
电影频道为了培养频道用户的心智,定义了头部和轮播的氛围色。
b) 页面整体都是氛围
高清频道突出高清适配的质感,将整个高清频道页面都定义了氛围色。
2、组件级别
分发场景有几十种组件,大部分组件接入非常便捷,只需将引用的资源字段改为 DesignToken 即可;
1) 常规组件
这里 Android 和 iOS 实现有些差异,需要分别说明。
a) Android
Android 为了提高性能,有些文本绘制使用了自定义 View 来单独绘制文本,目的是减少 View 数量和减少 View 的 measure 时间,上图的 PhoneCommonTitlesWidget 就是一个封装了标题绘制的自定义 View。
下面的代码主要是说明了如何使用 DesignToken,使用 DesignToken 色值有两种方式。
第一种,通过 Layout 布局文件使用包含 DesignToken 的 Style,这种方式方式一般用于系统控件,或继承自系统控件的简单封装比如 YKTextView:
<com.youku.resource.widget.YKTextView
android:id="@+id/title_context_1"
style="@style/text_view_1a" //style
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="start|center_vertical"
android:layout_marginLeft="@dimen/dim_6"
app:layout_goneMarginLeft="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/title_left_icon"
/>
<style name="text_view_1a">
<item name="android:textSize">@dimen/font_size_big1</item>
<item name="android:textStyle">bold</item>
<item name="android:singleLine">true</item>
<item name="android:includeFontPadding">false</item>
<item name="android:ellipsize">end</item>
<item name="android:textColor">@color/ykn_primary_info</item> //DesignToken颜色
</style>
复制代码
第二种,通过代码使用 DesignToken 色值,这种一般是自定义 View 通过 draw 的方式来渲染 UI,比如上图中的 PhoneCommonTitlesWidget。
Resources res = context.getResources();
//主标题字体size
int sDefaultTitleTextSize = res.getDimensionPixelSize(R.dimen.font_size_middle1);
//副标题字体size
int sDefaultSubtitleTextSize = res.getDimensionPixelSize(R.dimen.font_size_middle4);
//主标题DesignToken颜色
int sDefaultTitleTextColor = res.getColor(R.color.ykn_primary_info);
//副标题DesignToken颜色
int sDefaultSubtitleTextColor = res.getColor(R.color.ykn_tertiary_info);
复制代码
b) iOS
有些组件业务逻辑复杂、视图的层级很深,找到相应的代码非常耗时,我们可以使用 Xcode 的 Debug 工具、也可以使用优酷开发的一套 UI 检查工具(啄木鸟),来快速适配暗黑模式。
// 标题:一级信息色Token
_titleLabel.textColor = [UIColor ykn_primaryInfo];
// 子标题:二级信息色Token
_subtitleLabel.textColor = [UIColor ykn_secondaryInfo];
// 边框:升起的一级组块背景Token
_borderView.backgroundColor = [UIColor ykn_elevatedPrimaryBackground];
复制代码
2)特殊组件
优酷有些组件展示的主体是服务端下发的图片,但是服务端暂时不支持下发视频暗黑模式的图片。针对这种组件就需要有个降级策略,尽量保证暗黑模式的整体显示效果,这种属于特例情况要根据自己的业务特性来定义降级模式。
下面是一个具体的样例。
四、遇到的问题
1、Android
由于资源(color,drawable)是通用的,对于那些没有适配暗黑的页面要进行特殊处理。具体方式是: 在暗黑模式下进入没有适配暗黑的页面, 需要关闭暗黑模式来保证页面可以寻址到亮色的资源,在离开的时候再恢复暗黑模式。
在进入页面的时候关闭暗黑模式,Activity OnResume 中调用下面代码,关闭暗黑模式
//判断当前是否处在暗黑模式
if (UIMode.getInstance().isDarkMode()) {
//关闭暗黑模式
appCompatActivity.getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_NO);
//更新颜色配置
ColorConfigureManager.getInstance().onConfigureChanged();
}
复制代码
在离开或页面不可见时候恢复暗黑模式,Activity OnPause 中调用下面代码,恢复暗黑模式
//判断当前是否处在暗黑模式
if (UIMode.getInstance().isDarkMode()) {
//恢复暗黑模式
appCompatActivity.getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
//关闭暗黑模式
appCompatActivity.getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
//更新颜色配置
ColorConfigureManager.getInstance().onConfigureChanged();
复制代码
2、iOS
渐变色处理方式不友好
iOS 上渐变色无法自动切换暗黑样式,只能通过监听暗黑模式变化通知,手动设置暗黑渐变样式。
对比系统原生暗黑
五、适配效果
六、总结
暗黑模式对于视频类应用可以明显提升用户体验,在观感上更适合沉浸式的深度浏览,对于夜晚使用能有效避免亮色模式“刺眼”, 对眼睛更加舒适。因为 LED 屏幕在显示黑色像素的时候并不发光,因而可以提升续航时间,看剧更持久。
用户主路径涉及到的页面,弹窗,甚至图片默认打底图,Loading 图,页面加载出来前的骨架图尽量都适配暗黑模式。由于人眼对由暗到亮比由亮到暗更敏感(例如: 黑夜中突然的车灯会晃得睁不眼睛),主业务场景的暗黑模式尽量避免亮色模式的 UI 元素带来视觉反差,破坏用户沉浸式浏览观看体验。
对于 Android 而言,暗黑模式在系统级的支持是从 Android 10 开始,目前 Android 10 的手机还没有大规模的系统升级。在开发中我们可以通过代码强制打开暗黑模式,方便进行开发。产品上也可以考虑在客户端的设置项中增加“强制暗黑模式”的开关,使得低于 Android 10 版本的手机也可以使用暗黑模式。
作者简介:
叔平,阿里文娱无线高级开发工程师。
相关阅读:
优酷暗黑模式(一):是什么、为什么、如何落地?
优酷暗黑模式(二):如何建立设计语言标准化管理体系
优酷暗黑模式(三):暗黑模式设计指南
优酷暗黑模式(四):设计标准化的技术实现
优酷暗黑模式(五):暗黑模式的技术实现策略
优酷暗黑模式(六):暗黑模式的技术支撑 iOS
优酷暗黑模式(七):暗黑模式的技术支撑 Weex & H5
评论