热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

全网最详!暗黑模式在Trip.comApp的实践

作者简介本文为联合撰稿,作者为携程国际业务研发部UED团队静静,公共研发团队祥星、旭仔、俊仔、增翼。一、背景在2019年,随着iOS13与

作者简介

 

本文为联合撰稿,作者为携程国际业务研发部UED团队静静,公共研发团队祥星、旭仔、俊仔、增翼。


一、背景

在 2019 年,随着 iOS 13 与 Android Q 的推出,Apple 和 Google 同时推出主打功能暗黑模式,分别为 Dark Mode(iOS)/Dark Theme(Android) ,下文我们统称为 Dark Theme。在前期预研中,我们发现 66% 的 iOS 13 用户选择打开Dark Theme,可见用户对暗黑模式的喜爱和期待。

那么 Dark Theme 能带来哪些好处呢?

  • 更加省电,当代手机大部分都是OLED屏(OLED屏黑色下不发光更省电),配合Dark Theme 能耗更低;

  • 提供一致性的用户体验,当用户从Dark Theme的环境切换到我们的App,仍然能够享受黑色的宁静,避免亮眼的白色带来的刺激感;

  • 提升品牌形象,及时跟进系统新特性,在享受新特性带来美好之外还能获得Apple Store和Google Play推荐位机会,提升整体品牌形象;

  • 为弱视以及对强光敏感的用户提高可视性,让用户在暗环境中轻松使用App。

接下来,我们从视觉设计、实现方案和开发效率三个角度来介绍 Dark Theme 在 Trip.com App的实践。


二、视觉设计

暗黑模式是一套全新的设计风格,非简单的颜色明暗处理。我们将设计理念归结为三大要点,并介绍我们整体的设计思路。


2.1 三大要点

1)元素层级越高,表面颜色越浅

UI视觉层次致力于以一种用户能够快速理解的方式呈现产品内容,那么在 Dark Theme 下如何保证视觉层级依然有效呢?在 Light 模式中,我们使用带投影的白色卡片来模拟现实世界的空间深度感,而切换到 Dark 模式,则需要通过较浅的颜色表面来表示高度。层级越高,越接近于光源,表面的颜色就越浅。 

2)降低饱和度,提升可读性

设计 Dark Theme 时,尽量避免使用高饱和度的颜色,因为这些颜色会在深色背景上产生视觉抖动,导致人眼产生疲劳。以 Trip.com 的品牌蓝为例,若颜色不做调整,直接展示在深色背景上,不仅信息的清晰度降低了,而且识别的费力度还增高了。这显然不是我们所希望的,所以在 Dark Theme 下我们选择更低饱和的颜色来达到更好的可读性。 


3)增加对比度,提升可用性

依据 WCAG2.0 AA 设计标准,文本的视觉呈现以及文本图像至少要有4.5:1的对比度。深色表面选取白色文字达不到 AA 标准。 

2.2 设计方案

遵循上述设计要点,我们制定了 Trip.com 的颜色映射和插画设计方案。


2.2.1 颜色映射方案

为了规范化管理颜色库,保证产品、设计、开发的理解一致性,我们采用最直观的方式来命名颜色。这种方式既统一了 Light 和 Dark 的颜色命名,又降低了各方的沟通难度。具体的映射效果如下: 

UI中的彩色,统一进行了降饱和处理,这些彩色会应用于不同的场景,可能是背景,行动点,标签,或者是图标等等地方,那么当彩色用于背景时,为了确保文字和背景色有足够对比度,低饱和度的浅色背景就需要配合深色字一起使用。 


2.2.2 插画系统的设计

开启 Dark Theme,就像是我们把房间的窗帘拉上了,打开了一盏灯,不同层级高度的物体表面会受到不同的光照,表现出不同明暗的颜色。我们插画系统中的物体和人物沿用这种设计,在暗环境中,由于光线不够充足,人物的肤色会跟着变暗,衣服的颜色也会发生微妙的变化。比如白色、鲜亮的衣服,到了暗环境下,就会呈现灰色、低饱和度的暗色。

三、实现方案

Trip.com App 使用原生系统与 React Native 混合开发的模式。我们在各系统方案的基础上,结合 Trip.com 自身的特性,制定了一套iOS、Android和React Native三端的Dark Theme适配方案。


3.1 iOS

我们为 iOS 13 以上用户提供了两种主题模式的选择:

  • 自适应模式:跟随系统展示 Light/Dark 主题 

  • 强制 Light 模式:App 保持 Light 主题,不随系统主题变化


3.1.1 适配原理

iOS系统为 UIWindow、UIViewController、UIView 提供了overrideUserInterfaceStyle 属性来控制 Light/Dark 主题,所以我们只要控制 KeyWindow 的该属性,就可以控制整个 App 的主题。

3.1.2 适配方案


1)设置开关

App主题设置逻辑如图,KeyWindow 只有在App和系统都开启 Dark Theme 时,才会开启 Dark 主题。

跟随系统切换主题需要考虑到 App 运行时,系统主题被切换的情况:

  • 前往系统设置页手动切换 

  • 开启自动切换后,系统会自动更新主题 

这两种情况都需 App 进入后台,所以只需要添加 App 进入前台的监听,重复1的逻辑即可完成跟随系统变换主题的功能。


2)颜色适配

系统提供了 colorWithDynamicProvider 方法来适配 Light/Dark 模式下的颜色,我们依照视觉颜色映射方案封装颜色,覆盖绝大多数场景。部分无法通过动态色适配的场景,如 CGColor、RGB 颜色,可以通过 resolvedColorWithTraitCollection 方法解析出当前上下文所需要的颜色进行使用。


3)图片适配

系统早在 iOS12 就为 UITraitCollection 增加了 userInterface 属性,我们只要向 ImageAssets 注册 Light/Dark 下两种主题的图片,而后 UIImageView 根据 traitCollectionDidChange 变化自动获取 Light/Dark 图片。

App 内的静态图片资源可以通过 Images.xcassets 直接配置,通过网络下发或代码动态生成的图片可以通过 registerImage:withTraitCollection: 的方式进行动态注册。


4)注意事项

动态色或 ImageAssets 的原理都是根据容器的 userInterface 取得对应的内容,视图上的动态颜色或 ImageAssets 将根据视图的 userInterface 取值,App 内直接进行颜色计算或者图片处理的将会根据 UITraitCollection.currentColletion 进行取值。

设置 Window 的主题来完成 App 主题适配的工作,会存在 App 主题与系统主题不同步的情况,例如系统主题为 Dark,App 主题为 Light。此时直接对动态颜色或 ImageAssets 进行操作会取得错误的结果。所以对于这种场景,都不使用动态色或 ImageAssets,仅在发生主题切换时机进行视图刷新操作。


3.2 Android

我们不仅在 Android Q 上实现 Dark Theme,在 Android Q 以下的版本也适配了 Dark Theme。在 Android Q 上,用户可以选择跟随系统来展示 Dark Theme 或者强制关闭 Dark 保持 Light 主题。

在 Android Q 以下,我们也支持了 Dark Theme,用户可以选择强制打开或者强制关闭 Dark Theme。


3.2.1 适配原理

Android App 启动时会根据系统的配置加载不同的资源,以加载图片为例,高分辨率系统加载三倍图,低分辨率系统加载二倍图。同样地,系统也会根据 Dark Theme 的打开或者关闭来加载 Dark 或者 Light 资源。

我们会往 App 的 value 和 value-night 文件目录下放置 UED 提供的 Light 和 Dark 两套资源。当 App 打开 Dark Theme,系统选择从 value-night 目录加载资源,展示 Dark 界面;当 App 关闭 Dark Theme,系统选择从 value 目录加载资源,展示 Light 界面。


3.2.2 适配方案

我们通过开关设置、颜色适配、图片适配和其他注意事项四小节来介绍Android的Dark Theme适配方案。

1)开关设置

从上述代码可以看出,只有使用 AppCompat 的代码才具有 Dark Theme 特性,例如继承 AppCompatAcivity 和 AppCompatDialog 才支持 Dark Theme,而普通的 Activity 和 Dialog 不会展示 Dark Theme,同样地 Application 也不支持。

// 打开darkmode
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODENIGHTYES);// 关闭darkmode
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODENIGHTNO);// darkmode跟随系统
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODENIGHTFOLLOW_SYSTEM);

2)颜色适配

在 value 和 value-night 目录下定义 Light 和 Dark 相同名字的颜色,如下图: 

在 XML 或者代码中使用

//xml
android:textColor="@color/colorbrandingblue"//Java kotlin
ContextCompat.getColor(activity, R.color.colorbrandingblue)

 

注意:Activity 必须是 AppCompatActivity 实例,不能是 ApplicationContext/Activity。另外由于带透明度的颜色必须一个一个在 XML 声明,为了减轻开发工作量,我们提供了一个脚本可以快速生成 Light 和 Dark 下的透明度颜色。


3)图片适配

图片适配工作分资源图片适配和自定义 drawable 适配:

  • drawable/mipmap:在 drawable-xxhdpi 和 drawable-night-xxhdpi 目录下放置Light和Dark相同名字的图片,系统根据Light/Dark加载图片。

  • IconFont/自定义Shape/自定义Selector/SVG:因为绘制使用颜色,所以用法同颜色。


4)注意事项

  • 在非 AppCompatActivity 内展示 Dark Theme ,利用下面的代码可在非 AppCompatActivity 内展示 Dark 颜色。

public class IBUDarkModeDelegate {public static void applyNight(Context activity) {Activity conreteActivity = null;if (activity instanceof Activity) {conreteActivity = (Activity) activity;} else if (activity instanceof ThemedReactContext) {conreteActivity = (Activity) ((ThemedReactContext) activity).getBaseContext();}if (conreteActivity != null) {AppCompatDelegate appCompatDelegate = AppCompatDelegate.create(conreteActivity, new AppCompatCallback() {public ActionMode onWindowStartingSupportActionMode(ActionMode.Callback callback) {return null;}});appCompatDelegate.applyDayNight();}}
}// Activity创建前调用即可
protected void onCreate(Bundle savedInstanceState) { IBUDarkModeDelegate.applyNight(this); super.onCreate(savedInstanceState);
}

  • 颜色名必须全App唯一。

  • 切换手机系统的Dark Theme,会导致Activity重建,业务线按需做好状态保存恢复。

  • 做好全机型测试,防止个别机型出现异常展示问题。


3.3 ReactNative


3.3.1 适配方案

RN 桥接 Native 端,通过直接获取和动态监听两种方式获取 Native 端的主题变化。


1)从 Native 端获取当前的 theme 值

使用 Native Modules 的同步方法在 JS 端获取当前 theme 值,JS 端方法调用能直接得到 Native 同步方法的返回值,而非一个 Promise。

同步方法于 2017 年 1 月和 10 月先后被引入 ReactNative 的 Android 端和 iOS 端, 但直到现在,仍然没有被写入文档:

  • iOS: 使用 RCTEXPORTSYNCHRONOUSTYPEDMETHOD() 替换 RCTEXPORTMETHOD()(v0.51.0 及以上版本支持Commit)

  • Android: 在 @ReactMethod annotation 后面添加 (isBlockingSynchronousMethod = true) (v0.42.0 及以上版本支持Commit)

同步方法的缺点是无法在 Debug Remotely 时调用,所以必须在 Debug Remotely 时,提供默认值。我们接入 dark theme 时,选择了 dark 作为默认值。


2)theme 值变化监听

我们使用RN事件监听Theme变化。

  • iOS: RCTEventEmitter

  • Android: RCTDeviceEventEmitter


3)RN业务方调用 theme

我们提供 IBUThemeContext & IBUThemeProvider 两个类供产线获取主题。 Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。IBUThemeContext 是 Context 在 Theme 上的一个应用, IBUThemeProvider 负责同步 Theme 值,并将其传递给 IBUThemeContext.Provider。

// IBUThemeContext
export const IBUThemeContext &#61; React.createContext<&#39;light&#39; | &#39;dark&#39;>(&#39;light&#39;);
//IBUThemeProvider
export class IBUThemeProvider extends Component {// 引入文件时同步获取一次 themestatic theme: &#39;light&#39; | &#39;dark&#39; &#61; isInChromeDebugMode ? &#39;dark&#39; : IBUTheme.getTheme();constructor(props: IBUThemeProviderProps) {super(props);// 实例创建时, 再次同步一次themeconst theme &#61; isInChromeDebugMode ? &#39;dark&#39; : IBUTheme.getTheme();IBUThemeProvider.theme &#61; theme;this.state &#61; {theme,};}render(): JSX.Element {const { theme } &#61; this.state;const { children } &#61; this.props;return {children};}
}

将IBUThemeProvider 嵌入App 的根节点&#xff0c; 组件树便能通过如下两种方法&#xff0c;获取theme值: 

通过IBUThemeProvider.theme 读取全局theme。声明了static contextType&#61;IBUThemeContext 的类中使用 this.context&#xff0c;获取theme值。

4&#xff09;颜色适配

我们提供下列方法供产线使用颜色&#xff0c;方法支持透明度的设置&#xff1a;

export declare class IBUColor{static red(theme?: &#39;light&#39; | &#39;dark&#39;, alpha?: number): string;static green(theme?: &#39;light&#39; | &#39;dark&#39;, alpha?: number): string;static blue(theme?: &#39;light&#39; | &#39;dark&#39;, alpha?: number): string;
}

所有方法均接受 theme 和 alpha 两个可选参数&#xff0c; 方法会先根据 theme 选择对应颜色的 hex 字符串色值&#xff0c;如果 theme 值为空&#xff0c; 则 fallback 到 IBUThemeProvider.theme &#xff0c; 之后再根据 alpha 值计算颜色的的 alpha hex 值&#xff0c;并拼接到 hex 字符串色值之后。如 alpha 为空&#xff0c;则不拼接 hex 色值。最后将对应的 hex 色值字符串返回。


5&#xff09;图片适配

我们使用 lazy getters 解决 Light/Dark 图片展示的问题。方式如下&#xff1a;

RN端图片之前已经作了统一的静态资源管理&#xff1a;

export const images &#61; {button: require(&#39;./images/button.png&#39;),logo: require(&#39;./images/logo.png&#39;),
}

使用 lazy getters&#xff0c;稍作改造后&#xff0c;即能完美适配&#xff1a;

export const images &#61; {get button() {const theme &#61; IBUThemeProvider.theme;return theme &#61;&#61;&#61; &#39;dark&#39; ? require(&#39;./images/button_dark.png&#39;) : require(&#39;./images/button.png&#39;);},get logo() {const theme &#61; IBUThemeProvider.theme;return theme &#61;&#61;&#61; &#39;dark&#39; ? require(&#39;./images/logo_dark.png&#39;) : require(&#39;./images/logo.png&#39;);}
}

6&#xff09;DynamicStyle

ReactNative 导出的 StyleSheet 只会在文件引入时&#xff0c;初始化一次&#xff0c;不会随着 App DarkTheme 的变化而变化这就导致系统主题发生变化时&#xff0c;RN 无法更新 styles&#xff0c;导致 RN 页面与 Native 不一致的问题。为此我们提出 DynamicStyleSheet 来解决该问题。

type IBUNamedStyles &#61; { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };
export function IBUDynamicStyleSheet | IBUNamedStyles>(callback: () &#61;> T | IBUNamedStyles
): (theme?: &#39;light&#39; | &#39;dark&#39;) &#61;> T {const cache: { light?: T; dark?: T } &#61; {light: undefined,dark: undefined,};return (theme?: &#39;light&#39; | &#39;dark&#39;): T &#61;> {const currentTheme &#61; theme || IBUThemeProvider.theme;let style &#61; cache[currentTheme];if (!style) {style &#61; StyleSheet.create(callback());cache[currentTheme] &#61; style;}return style;};
}

IBUDynamicStyleSheet 是一个 Function&#xff0c;它接受一个返回值是 style 的 Function 作为参数&#xff0c;并且返回一个 Function。这种 Function 也被称High Order Function。

StyleSheet 创建 style 的代码被包在参数的 Function 中&#xff0c;这样可以保证每次取值都会取到当前的 theme 对应的 style。每次 render 前, 将返回的 Function 执行一次&#xff0c;并将这个 Function 的返回值作为真正的 style 使用。

IBUDynamicStyleSheet 内部对 light 和 dark 下的 style 作了缓存&#xff0c;这样大部分情况下 style 仍然只会被创建一次&#xff0c; theme 发生变化时 style 被创建两次&#xff0c; theme 发生多次变化时&#xff0c;style 最多只被创建两次。

采用DynamicStyleSheet这种方式&#xff0c;代码改动量不仅小&#xff0c; 而且性能损失少&#xff0c; 达到实时切换Theme的目的。


7&#xff09;Examples

App 开启dark theme

export default class App extends Component{render(){return (// ...)}
}

Class Component 接入

class MyClass extends React.Component {//需要声明contextType, 否则该组件可能不会随theme变化而重新绘制static contextType &#61; IBUThemeContext;constructor(props, context) {super(props, context)// context can be accessed now, https://github.com/facebook/react/issues/6598const theme &#61; this.context;// ....}// ...render() {const theme &#61; this.context; // &#39;light&#39;|&#39;dark&#39;/* render something based on the value of IBUThemeContext */const styles &#61; dynamicStyles(theme);return({/* render something else */})}
}
const dynamicStyles &#61; IBUDynamicStyleSheet(() &#61;> ({icon: {backgroundColor: IBUColor.quaternaryGray(),height: 20,},
}));

Functional Component接入

export const MyComponent &#61; () &#61;> {const theme &#61; React.useContext(IBUThemeContext); // &#39;light&#39;|&#39;dark&#39;const styles &#61; dynamicStyles(theme);return ({/* render something else */})
}
const dynamicStyles &#61; IBUDynamicStyleSheet(() &#61;> ({icon: {backgroundColor: IBUColor.quaternaryGray(),height: 20,},
}));

注意&#xff1a;Component必须声明contextType, 否则不能在theme发生变化时触发render重绘。


四、工具&效率

在建立颜色规范到方案落地的过程中&#xff0c;我们发现新的颜色命名虽然容易理解&#xff0c;由于对使用的名字命名&#xff0c;开发在使用时需要对照视觉稿查找对应的颜色命名&#xff0c;造成开发效率上的浪费。

例如视觉稿上显示 #287DFA&#xff0c;开发根据色值查找此颜色的映射名称 brandingBlue&#xff0c;再将颜色设置成 brandingBlue。

为了解决此问题&#xff0c;我们扩展了 Sketch Measure 插件&#xff0c;颜色一栏不再展示颜色的色值&#xff0c;取而代之的是颜色的命名。这样开发能依照视觉稿直接获取颜色名&#xff0c;大大减少工作量。

插件效果如下 &#xff1a;

至此完美解决了开发适配 Dark Theme 的效率问题。


五、结语

Dark Theme适配是一项涉及多职能部门合作的项目。在规范的设计指导、完善的落地方案和便捷的效率工具加持下&#xff0c;我们的适配成本和资源大大降低。在各端仅投入一位研发人员的情况下&#xff0c;在两周内完成了从方案制定到方案落地&#xff0c;并推进产线接入。

Trip.com一直致力于追随前沿新特性&#xff0c;带给用户最好的体验&#xff0c;让用户更舒适&#xff0c;旅行从此简单。

参考资料

1&#xff09;Apple Dark Mode介绍&#xff1a;

https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/dark-mode/ 

2&#xff09;Implementing Dark Mode on iOS - WWDC2019&#xff1a;

https://developer.apple.com/videos/play/wwdc2019/214/ 

3&#xff09;Android Dark Theme 介绍&#xff1a;

https://developer.android.com/guide/topics/ui/look-and-feel/darktheme

4&#xff09;React Native 参考&#xff1a;

https://github.com/react-native-community/discussions-and-proposals/pull/11#discussion_r210370835 https://github.com/facebook/reactnative/commit/63fa3f21c5ab308def450bffb22054241a8842ef#diff-55c2992d993407398c62bf19f803088f 

https://github.com/Lxxyx/react-native-dynamic-stylesheet https://developer.mozilla.org/enUS/docs/Web/Javascript/Reference/Functions/get 

https://medium.com/fantageek/how-to-structure-your-project-and-manage-static-resources-in-react-native-6f4cfc947d92 

https://willowtreeapps.com/ideas/react-native-tips-and-tricks-2-0-managing-static-assets-with-absolute-paths 

5&#xff09;WCAG21视觉标准&#xff1a;

https://www.w3.org/TR/WCAG21/#contrast-enhanced

团队招聘信息

携程国际业务基础研发部&#xff0c;负责移动应用 Trip.com 的开发和维护。感兴趣的小伙伴欢迎投递简历到yu.yinga&#64;trip.com&#xff0c;邮件标题&#xff1a;【携程国际业务基础研发部】&#43;【姓名】

【推荐阅读】

  • 携程酒店安卓地图开发实践

  • 携程机票 App Kotlin Multiplatform 初探

  • 携程无线APM升级实践

  • 携程酒店iOS动态View的探索
  • 《携程技术2019年度合辑》&#xff0c;送给爱学习的你

 “携程技术”公众号

  分享&#xff0c;交流&#xff0c;成长


推荐阅读
  • Question该提问来源于开源项目:react-native-device-info/react-native-device-info ... [详细]
  • Android获取app应用程序大小的方法
    Android获取app应用程序大小的方法-Android对这种方法进行了封装,我们没有权限去调用这个方法,所以我们只能通过AIDL,然后利用Java的反射机制去调用系统级的方法。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • Android源码中的Builder模式及其作用
    本文主要解释了什么是Builder模式以及其作用,并结合Android源码来分析Builder模式的实现。Builder模式是将产品的设计、表示和构建进行分离,通过引入建造者角色,简化了构建复杂产品的流程,并且使得产品的构建可以灵活适应变化。使用Builder模式可以解决开发者需要关注产品表示和构建步骤的问题,并且当构建流程发生变化时,无需修改代码即可适配新的构建流程。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • React基础篇一 - JSX语法扩展与使用
    本文介绍了React基础篇一中的JSX语法扩展与使用。JSX是一种JavaScript的语法扩展,用于描述React中的用户界面。文章详细介绍了在JSX中使用表达式的方法,并给出了一个示例代码。最后,提到了JSX在编译后会被转化为普通的JavaScript对象。 ... [详细]
  • 本文介绍了iOS开发中检测和解决内存泄漏的方法,包括静态分析、使用instruments检查内存泄漏以及代码测试等。同时还介绍了最能挣钱的行业,包括互联网行业、娱乐行业、教育行业、智能行业和老年服务行业,并提供了选行业的技巧。 ... [详细]
  • 详解react组件通讯方式(多种)
    这篇文章主要介绍了详解react组件通讯方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着 ... [详细]
author-avatar
mb97ujp
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有