鸿蒙生态的设备种类越来越多:手机、平板、折叠屏、车机、智慧屏。同一个应用跑在不同设备上,交互方式、屏幕尺寸、使用场景完全不一样。比如音乐APP,手机上是竖屏列表,平板上要做横屏多栏布局,车机上要做大字体、少操作的驾驶模式。如果不做设备判断,一套布局走天下,车机上按钮太小点不准,平板上两边全是空白,体验都很差。本文基于最新鸿蒙API,讲清楚怎么判断当前设备类型、怎么根据设备做差异化业务逻辑,代码可以直接复制到项目里用。
01 为什么要判断设备类型
很多人第一反应是:做响应式布局不就行了,根据屏幕宽度自动调整。大部分场景响应式确实够用,但有些业务逻辑层面的差异,光靠布局适配解决不了。
**交互方式不同。**手机是手指触控,平板是手指加触控笔,车机是旋钮加语音,智慧屏是遥控器。同样的按钮,手机上做小一点没事,车机上按钮小了驾驶的时候根本点不准。
**功能裁剪不同。**车机上不需要相册、不需要拍照,这些功能直接隐藏。智慧屏上没有传感器,定位、加速度这些能力直接关掉。
**默认布局不同。**平板默认横屏使用,布局要按横屏设计;手机默认竖屏;车机固定横屏,宽度很宽高度很窄。
**性能和资源不同。**车机芯片性能普遍弱一些,动画效果、高清图片要降级处理,不然卡顿。
所以实际项目里,一般是响应式布局加设备类型判断双管齐下:布局靠断点自适应,功能和交互逻辑靠设备类型判断。
02 设备类型判断API
鸿蒙提供了deviceInfo模块,在@kit.BasicServicesKit里,直接能拿到当前设备的类型信息。
typescript
import { deviceInfo } from '@kit.BasicServicesKit';
// 获取设备类型
async function getDeviceType() {
const deviceType = deviceInfo.deviceType;
console.info('当前设备类型:', deviceType);
return deviceType;
}
deviceType返回的是字符串,常见的取值:
-
phone:手机
-
tablet:平板
-
car:车机
-
tv:智慧屏
-
wearable:手表
-
2in1:二合一笔记本
这个是系统直接返回的,不用自己猜。比根据屏幕宽度判断准确多了------同样宽度的平板和折叠屏展开态,系统能明确告诉你哪个是平板哪个是手机。
除了设备类型,还能拿到更多设备信息:
typescript
import { deviceInfo } from '@kit.BasicServicesKit';
function getDeviceInfo() {
console.info('设备类型:', deviceInfo.deviceType);
console.info('设备名称:', deviceInfo.deviceName);
console.info('品牌:', deviceInfo.brand);
console.info('产品型号:', deviceInfo.productModel);
console.info('系统版本:', deviceInfo.osFullName);
console.info('是否仿真器:', deviceInfo.isDebug);
}
deviceName和productModel可以用来做更精细的判断,比如某一款车机屏幕特别窄,单独做适配。但一般业务场景下,用deviceType就够了。
03 设备屏幕信息获取
除了设备类型,还要拿到屏幕的物理尺寸、分辨率,这样才能做精准的布局判断。用display模块。
typescript
import { display } from '@kit.ArkUI';
function getScreenInfo() {
const displayInfo = display.getDefaultDisplaySync();
console.info('屏幕宽度(px):', displayInfo.width);
console.info('屏幕高度(px):', displayInfo.height);
console.info('屏幕密度:', displayInfo.densityDPI);
console.info('字体比例:', displayInfo.fontSizeScale);
// 转换成vp单位
const density = displayInfo.densityDPI / 160;
const widthVp = displayInfo.width / density;
const heightVp = displayInfo.height / density;
console.info('屏幕宽度(vp):', widthVp);
console.info('屏幕高度(vp):', heightVp);
}
屏幕尺寸用来做什么?判断是横屏还是竖屏,判断是大屏还是小屏。车机一般是宽高比很大的横屏,高度比较矮。手机竖屏是高宽比大。
typescript
function getScreenOrientation(): string {
const displayInfo = display.getDefaultDisplaySync();
if (displayInfo.width > displayInfo.height) {
return 'landscape'; // 横屏
} else {
return 'portrait'; // 竖屏
}
}
04 差异化业务逻辑实现
知道设备类型之后,怎么落地差异化逻辑?核心思路是:公共逻辑写一套,设备特有的逻辑单独分支。
typescript
import { deviceInfo } from '@kit.BasicServicesKit';
class DeviceConfig {
private static instance: DeviceConfig;
private deviceType: string = 'phone';
static getInstance(): DeviceConfig {
if (!DeviceConfig.instance) {
DeviceConfig.instance = new DeviceConfig();
}
return DeviceConfig.instance;
}
init() {
this.deviceType = deviceInfo.deviceType;
console.info('当前设备:', this.deviceType);
}
isPhone(): boolean {
return this.deviceType === 'phone';
}
isTablet(): boolean {
return this.deviceType === 'tablet';
}
isCar(): boolean {
return this.deviceType === 'car';
}
isTV(): boolean {
return this.deviceType === 'tv';
}
// 不同设备的按钮大小
getButtonSize(): number {
if (this.isCar()) return 80; // 车机按钮大一点
if (this.isTablet()) return 56; // 平板中等
return 44; // 手机标准
}
// 不同设备的列表列数
getGridColumns(): number {
if (this.isTablet()) return 3; // 平板3列
if (this.isCar()) return 4; // 车机4列
return 2; // 手机2列
}
// 不同设备默认横竖屏
getDefaultOrientation(): string {
if (this.isCar()) return 'landscape'; // 车机横屏
if (this.isTablet()) return 'landscape'; // 平板横屏
return 'portrait'; // 手机竖屏
}
}
export const deviceConfig = DeviceConfig.getInstance();
在EntryAbility启动的时候初始化一次:
typescript
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { deviceConfig } from '../utils/DeviceConfig';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, param: AbilityConstant.LaunchParam): void {
// 启动时初始化设备信息
deviceConfig.init();
}
}
页面里用的时候直接调:
typescript
@Entry
@Component
struct MusicPage {
build() {
Column() {
// 车机模式:大字体大按钮,少操作
if (deviceConfig.isCar()) {
Text('驾驶模式')
.fontSize(32)
Button('下一首')
.width(200)
.height(80)
.fontSize(28)
} else {
// 手机/平板:正常布局
Text('音乐列表')
.fontSize(20)
Button('播放')
.width(120)
.height(44)
}
}
}
}
这样代码里到处都是if判断也不好看,可以把不同设备的页面组件分开,根据设备类型直接加载不同的组件:
typescript
@Entry
@Component
struct HomePage {
build() {
if (deviceConfig.isCar()) {
CarHomeComponent()
} else if (deviceConfig.isTablet()) {
TabletHomeComponent()
} else {
PhoneHomeComponent()
}
}
}
@Component
struct CarHomeComponent {
build() {
// 车机专属首页
Column() {
Text('车机首页')
}
}
}
@Component
struct TabletHomeComponent {
build() {
// 平板专属首页
Row() {
Text('平板首页')
}
}
}
@Component
struct PhoneHomeComponent {
build() {
// 手机专属首页
Column() {
Text('手机首页')
}
}
}
设备差异特别大的页面,直接拆成三个组件,互不影响,好维护。设备差异小的,就在同一个组件里用if判断样式参数就行。
05 车机特殊适配
车机是最特殊的设备,和手机逻辑差很多,单独拿出来说。
**第一,驾驶模式要大字体、大按钮。**开车的时候视线不能离开路面太久,按钮要大,字要大,操作步骤要少。一个功能最好一两个按钮就能完成。
**第二,高频功能前置,低频功能藏起来。**音乐、导航、电话这些开车常用的,放首页显眼位置。设置、账号这些低频的,放到二级页面去。
**第三,默认横屏,不能旋转。**车机屏幕是固定装在车上的,方向不能变。在module.json5里把orientation设成landscape,强制横屏。
**第四,简化动画和效果。**车机芯片性能一般,复杂的动画、模糊效果、阴影都要简化,不然会卡。
typescript
// 车机专属配置
const carConfig = {
fontSize: 28, // 字体大一点
buttonHeight: 80, // 按钮高一点
animationEnabled: false, // 关动画
listColumns: 3, // 列表少几列
showAds: false // 不展示广告
};
// 手机配置
const phoneConfig = {
fontSize: 16,
buttonHeight: 44,
animationEnabled: true,
listColumns: 2,
showAds: true
};
06 完整工具类封装
把设备类型、屏幕信息、配置参数都整合到一个工具类里,项目里直接用。
typescript
import { deviceInfo } from '@kit.BasicServicesKit';
import { display } from '@kit.ArkUI';
class DeviceUtil {
private static instance: DeviceUtil;
private deviceType: string = 'phone';
private screenWidth: number = 0;
private screenHeight: number = 0;
static getInstance(): DeviceUtil {
if (!DeviceUtil.instance) {
DeviceUtil.instance = new DeviceUtil();
}
return DeviceUtil.instance;
}
init() {
this.deviceType = deviceInfo.deviceType;
const d = display.getDefaultDisplaySync();
const density = d.densityDPI / 160;
this.screenWidth = d.width / density;
this.screenHeight = d.height / density;
}
getType(): string {
return this.deviceType;
}
isPhone(): boolean {
return this.deviceType === 'phone';
}
isTablet(): boolean {
return this.deviceType === 'tablet';
}
isCar(): boolean {
return this.deviceType === 'car';
}
isTV(): boolean {
return this.deviceType === 'tv';
}
// 是否大屏
isLargeScreen(): boolean {
return this.screenWidth >= 600;
}
// 是否横屏
isLandscape(): boolean {
return this.screenWidth > this.screenHeight;
}
// 根据设备返回主题配置
getThemeConfig() {
if (this.isCar()) {
return { fontSize: 28, padding: 24, radius: 16, animation: false };
} else if (this.isTablet()) {
return { fontSize: 18, padding: 16, radius: 12, animation: true };
} else {
return { fontSize: 14, padding: 12, radius: 8, animation: true };
}
}
}
export const deviceUtil = DeviceUtil.getInstance();
在Ability启动时调用一次init(),之后页面里直接deviceUtil.isCar()、deviceUtil.getThemeConfig()就行,不用重复获取设备信息。
落地注意事项
**不要只靠屏幕宽度判断设备。**折叠屏展开之后宽度和平板差不多,但本质还是手机。用deviceType判断才准确,宽度只用来做响应式布局。
**设备差异不要散落在各个页面。**所有设备相关的配置都集中在DeviceUtil里,统一管理。不然这里写一个if、那里写一个else,以后改配置要翻遍整个项目。
**新设备类型要预留扩展。**以后可能出手表、智慧屏、智能冰箱,工具类里加个新的判断方法就行,不用到处改代码。
**车机和手机不要共用太多页面。**差异太大的页面,直接拆成两个组件。硬套一套逻辑,加一堆if判断,最后代码自己都看不懂。
小结
设备类型判断的核心不是写多少个if分支,而是把设备相关的配置统一管理。启动的时候初始化一次设备信息,页面里直接调用工具类的方法获取配置,不用重复获取。实际项目中,手机和平板差异不大,用响应式布局加少量设备判断就够了。车机差异大,单独做一套页面和交互。这样一套代码跑多种设备,开发量不会增加太多,用户体验却好很多。