提交 391f80a0 编写于 作者: 耿文广 提交者: geng-wenguang

Merge branch 'master' of gitee.com:openharmony/docs into master

Signed-off-by: N耿文广 <gengwenguang@huawei.com>
......@@ -48,21 +48,18 @@ On the widget page, the **postCardAction** API can be used to trigger a message
import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
onFormEvent(formId, message) {
onFormEvent(formId: string, message: string) {
// Called when a specified message event defined by the form provider is triggered.
console.info(`FormAbility onEvent, formId = ${formId}, message: ${JSON.stringify(message)}`);
let formData = {
'title':'Title Update.', // It matches the widget layout.
'detail':'Description update success.', // It matches the widget layout.
};
let formData = new Map<Object, string>();
formData.set('title', 'Title Update.'); // It matches the widget layout.
formData.set('detail', 'Description update success.'); // It matches the widget layout.
let formInfo = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo).then((data) => {
console.info('FormAbility updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('FormAbility updateForm failed: ' + JSON.stringify(error));
})
}
...
}
```
......
......@@ -779,7 +779,9 @@ interface Employee extends Identity, Contact {}
**Severity: error**
ArkTS does not support the returning `this` type. Use explicit type instead.
ArkTS does not support type notation using the `this` keyword (for example,
specifying a method's return type `this` is not allowed). Use explicit type
instead.
**TypeScript**
......@@ -959,7 +961,9 @@ type N = number
**Severity: error**
ArkTS does not support indexed access for class fields. Use dot notation
instead.
instead. An exception are all typed arrays from the standard library
(for example, `Int32Array`), which support access to their elements through
`container[index]` syntax.
**TypeScript**
......@@ -975,6 +979,9 @@ let x = p["x"]
class Point {x: number = 0; y: number = 0}
let p: Point = {x: 1, y: 2}
let x = p.x
let arr = new Int32Array(1)
console.log(arr[0])
```
## Recipe: Structural identity is not supported
......@@ -1541,33 +1548,6 @@ let a2: C[] = [{n: 1, s: "1"}, {n: 2, s : "2"}] // ditto
* Recipe: Object literal must correspond to some explicitly declared class or interface
* Recipe: Object literals cannot be used as type declarations
## Recipe: Lambdas require explicit type annotation for parameters
**Rule `arkts-explicit-param-types-in-lambdas`**
**Severity: error**
Currently, ArkTS requires the types of lambda parameters
to be explicitly specified.
**TypeScript**
```typescript
// Compile-time error only with noImplicitAny:
let f = (s /* type any is assumed */) => {
console.log(s)
}
```
**ArkTS**
```typescript
// Explicit types for lambda parameters are mandatory:
let f = (s: string) => {
console.log(s)
}
```
## Recipe: Use arrow functions instead of function expressions
**Rule `arkts-no-func-expressions`**
......@@ -1989,9 +1969,9 @@ let f = "string" + true // "stringtrue"
let g = (new Object()) + "string" // "[object Object]string"
let i = true + true // JS: 2, TS: compile-time error
let j = true + 2 // JS: 3, TS: compile-time error
let k = E.E1 + true // JS: 1, TS: compile-time error
let i = true + true // compile-time error
let j = true + 2 // compile-time error
let k = E.E1 + true // compile-time error
```
**ArkTS**
......@@ -2011,7 +1991,7 @@ let g = (new Object()).toString() + "string"
let i = true + true // compile-time error
let j = true + 2 // compile-time error
let j = true + 2 // compile-time error
let k = E.E1 + true // compile-time error
```
......@@ -2341,7 +2321,8 @@ iterate over data.
**Severity: error**
ArkTS supports the iteration over arrays and strings by the `for .. of` loop,
but does not support the iteration of objects content.
but does not support the iteration of objects content. All typed arrays from
the standard library (for example, `Int32Array`) are also supported.
**TypeScript**
......@@ -2433,7 +2414,8 @@ console.log("Area: ", Math.PI * r * r)
**Severity: error**
ArkTS supports `case` statements that contain only compile-time values.
ArkTS supports `case` statements that contain only compile-time values,
top-level scope `const` values, and `static readonly` class fields.
Use `if` statements as an alternative.
**TypeScript**
......@@ -2907,7 +2889,8 @@ function main(): void {
The only supported scenario for the spread operator is to spread an array into
the rest parameter. Otherwise, manually “unpack” data from arrays and objects,
where necessary.
where necessary. All typed arrays from the standard library (for example,
`Int32Array`) are also supported.
**TypeScript**
......@@ -3880,34 +3863,6 @@ let ce = new CustomError()
* Recipe: Prototype assignment is not supported
## Recipe: Runtime import expressions are not supported
**Rule `arkts-no-runtime-import`**
**Severity: error**
ArkTS does not support such “runtime” import expressions as `await import...`
because in the language import is a compile-time, not a runtime feature.
Use regular import syntax instead.
**TypeScript**
```typescript
const zipUtil = await import("utils/create-zip-file")
```
**ArkTS**
```typescript
import { zipUtil } from "utils/create-zip-file"
```
**See also**
* Recipe: Wildcards in module names are not supported
* Recipe: Universal module definitions (UMD) are not supported
* Recipe: Import assertions are not supported
## Recipe: Definite assignment assertions are not supported
**Rule `arkts-no-definite-assignment`**
......@@ -4065,8 +4020,7 @@ M.abc = 200
**Severity: error**
Currently ArkTS does not support utility types from TypeScript extensions to the
standard library (`Omit`, `Pick`, etc.). Exceptions are: `Partial`,
`Record`.
standard library, except following: `Partial`, `Record`.
For the type *Record<K, V>*, the type of an indexing expression *rec[index]* is
of the type *V | undefined*.
......@@ -4349,7 +4303,6 @@ import { something } from "module"
* Recipe: Wildcards in module names are not supported
* Recipe: Universal module definitions (UMD) are not supported
* Recipe: Runtime import expressions are not supported
## Recipe: Usage of standard library is restricted
......@@ -4364,9 +4317,7 @@ the following APIs is prohibited:
Properties and functions of the global object: `eval`,
`Infinity`, `NaN`, `isFinite`, `isNaN`, `parseFloat`, `parseInt`,
`encodeURI`, `encodeURIComponent`, `Encode`,
`decodeURI`, `decodeURIComponent`, `Decode`,
`escape`, `unescape`, `ParseHexOctet`
`Encode`, `Decode`, `ParseHexOctet`
`Object`: `__proto__`, `__defineGetter__`, `__defineSetter__`,
`__lookupGetter__`, `__lookupSetter__`, `assign`, `create`,
......@@ -4561,3 +4512,54 @@ class BugReport {
}
```
## Recipe: Classes cannot be used as objects
**Rule `arkts-no-classes-as-obj`**
**Severity: error**
ArkTS does not support using classes as objects (assigning them to variables,
etc.) because in ArkTS, a `class` declaration introduces a new type,
not a value.
**TypeScript**
```typescript
class C {
s: string = ""
n: number = 0
}
let c = C
```
## Recipe: `import` statements after other statements are not allowed
**Rule `arkts-no-misplaced-imports`**
**Severity: error**
In ArkTS, all `import` statements should go before all other statements
in the program.
**TypeScript**
```typescript
class C {
s: string = ""
n: number = 0
}
import foo from "module1"
```
**ArkTS**
```typescript
import foo from "module1"
class C {
s: string = ""
n: number = 0
}
```
......@@ -66,11 +66,14 @@ import fs from '@ohos.file.fs';
try {
let fd = fs.openSync('/path/to/form.png');
let obj = {
'temperature': '21°',
'formImages': { 'image': fd }
};
formBindingData.createFormBindingData(obj);
let createFormBindingDataParam = new Map<Object, string | object>()
let formImagesParam = new Map<Object, object>()
formImagesParam.set('image', fd)
createFormBindingDataParam.set("name", '21°')
createFormBindingDataParam.set('formImages', formImagesParam)
formBindingData.createFormBindingData(createFormBindingDataParam);
} catch (error) {
console.error(`catch error, code: ${error.code}, message: ${error.message}`);
}
......
......@@ -50,18 +50,24 @@ Displays a notification.
| options | ShowNotificationOptions | No| Notification title.|
**Example**
```javascript
export default {
show() {
notification.show({
contentTitle: 'title info',
contentText: 'text',
clickAction: {
bundleName: 'com.example.testapp',
abilityName: 'notificationDemo',
uri: '/path/to/notification'
}
});
}
```ts
class NotificationClass {
show: Function
}
let notificationObj: NotificationClass = {
show() {
notification.show({
contentTitle: 'title info',
contentText: 'text',
clickAction: {
bundleName: 'com.example.testapp',
abilityName: 'notificationDemo',
uri: '/path/to/notification'
}
});
}
}
export default notificationObj
```
......@@ -46,21 +46,18 @@
import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
onFormEvent(formId, message) {
onFormEvent(formId: string, message: string) {
// Called when a specified message event defined by the form provider is triggered.
console.info(`FormAbility onEvent, formId = ${formId}, message: ${JSON.stringify(message)}`);
let formData = {
'title': 'Title Update.', // 和卡片布局中对应
'detail': 'Description update success.', // 和卡片布局中对应
};
let formData = new Map<Object, string>();
formData.set('title', 'Title Update.'); // 和卡片布局中对应
formData.set('detail', 'Description update success.'); // 和卡片布局中对应
let formInfo = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo).then((data) => {
console.info('FormAbility updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('FormAbility updateForm failed: ' + JSON.stringify(error));
})
}
...
}
```
......
......@@ -30,20 +30,25 @@
1. 查询设备列表。
```js
var matchDevice;
```ts
import deviceManager from '@ohos.driver.deviceManager';
import { BusinessError } from '@ohos.base';
let matchDevice : deviceManager.USBDevice = null;
try {
let devices = deviceManager.queryDevices(deviceManager.BusType.USB);
for (let item of devices) {
let device = item as deviceManager.USBDevice;
let devices : Array<Device> = deviceManager.queryDevices(deviceManager.BusType.USB);
for (let item : Device of devices : Array<Device>) {
let device : deviceManager.USBDevice = item as deviceManager.USBDevice;
// 通过productId和vendorId来匹配要使用的USB设备
if (device.productId == 1234 && device.vendorId === 2345) {
matchDevice = device;
break;
}
}
} catch (error) {
console.error('Failed to query device. Code is ${error.code}, message is ${error.message}');
} catch (error : BusinessError) {
let errCode = (error as BusinessError).code;
let message = (error as BusinessError).message;
console.error(`Failed to query device. Code is ${errCode}, message is ${message}`);
}
if (!matchDevice) {
console.error('No match device');
......@@ -53,53 +58,69 @@
2. 绑定相应的设备。
```js
var remoteObject;
```ts
import deviceManager from '@ohos.driver.deviceManager';
import { BusinessError } from '@ohos.base';
let remoteObject : Object;
try {
deviceManager.bindDevice(matchDevice.deviceId, (error, data) => {
deviceManager.bindDevice(matchDevice.deviceId, ((error : BusinessError), (data : Object)) => {
console.error('Device is disconnected');
}, (error, data) => {
}, ((error : BusinessError), (data : Object)) => {
if (error) {
console.error('bindDevice async fail. Code is ${error.code}, message is ${error.message}');
console.error(`bindDevice async fail. Code is ${error.code}, message is ${error.message}`);
return;
}
console.info('bindDevice success');
remoteObject = data.remote;
});
} catch (error) {
console.error('bindDevice fail. Code is ${error.code}, message is ${error.message}');
} catch (error : BusinessError) {
let errCode = (error as BusinessError).code;
let message = (error as BusinessError).message;
console.error(`bindDevice fail. Code is ${errCode}, message is ${message}`);
}
```
3. 绑定成功后使用设备驱动能力。
```js
let option = new rpc.MessageOption();
let data = rpc.MessageSequence.create();
let repy = rpc.MessageSequence.create();
```ts
import deviceManager from '@ohos.driver.deviceManager';
import { BusinessError } from '@ohos.base';
let option : MessageOption = new rpc.MessageOption();
let data : MessageSequence = rpc.MessageSequence.create();
let reply : MessageSequence = rpc.MessageSequence.create();
data.writeString('hello');
let code = 1;
// code和data内容取决于驱动提供的接口
remoteObject.sendMessageRequest(code, data, reply, option)
.then(result => {
.then((result : string) => {
console.info('sendMessageRequest finish.');
}).catch(error => {
console.error('sendMessageRequest fail. code:' + error.code);
}).catch((error : BusinessError) => {
let errCode = (error as BusinessError).code;
console.error('sendMessageRequest fail. code:' + errCode);
});
```
4. 设备使用完成,解绑设备。
```js
```ts
import deviceManager from '@ohos.driver.deviceManager';
import { BusinessError } from '@ohos.base';
try {
deviceManager.unbindDevice(matchDevice.deviceId, (error, data) => {
if (error) {
console.error('unbindDevice async fail. Code is ${error.code}, message is ${error.message}');
deviceManager.unbindDevice(matchDevice.deviceId, (error : BusinessError, data : Object) => {
if (error : BusinessError) {
let errCode = (error as BusinessError).code;
let message = (error as BusinessError).message;
console.error(`unbindDevice async fail. Code is ${errCode}, message is ${message}`);
return;
}
console.info('unbindDevice success');
});
} catch (error) {
console.error('unbindDevice fail. Code is ${error.code}, message is ${error.message}');
} catch (error : BusinessError) {
let errCode = (error as BusinessError).code;
let message = (error as BusinessError).message;
console.error('unbindDevice fail. Code is ${errCode}, message is ${message}');
}
```
......@@ -149,7 +149,7 @@
以导航场景为例,实例化方式如下:
```ts
let requestInfo = {'scenario': geoLocationManager.LocationRequestScenario.NAVIGATION, 'timeInterval': 0, 'distanceInterval': 0, 'maxAccuracy': 0};
let requestInfo:geoLocationManager.LocationRequest = {'scenario': geoLocationManager.LocationRequestScenario.NAVIGATION, 'timeInterval': 0, 'distanceInterval': 0, 'maxAccuracy': 0};
```
**方式二:**
......@@ -179,14 +179,14 @@
以定位精度优先策略为例,实例化方式如下:
```ts
let requestInfo = {'priority': geoLocationManager.LocationRequestPriority.ACCURACY, 'timeInterval': 0, 'distanceInterval': 0, 'maxAccuracy': 0};
let requestInfo:geoLocationManager.LocationRequest = {'priority': geoLocationManager.LocationRequestPriority.ACCURACY, 'timeInterval': 0, 'distanceInterval': 0, 'maxAccuracy': 0};
```
4. 实例化Callback对象,用于向系统提供位置上报的途径。
应用需要自行实现系统定义好的回调接口,并将其实例化。系统在定位成功确定设备的实时位置结果时,会通过该接口上报给应用。应用程序可以在接口的实现中完成自己的业务逻辑。
```ts
let locationChange = (location) => {
let locationChange = (location:geoLocationManager.Location):void => {
console.log('locationChanger: data: ' + JSON.stringify(location));
};
```
......@@ -209,10 +209,11 @@
```ts
import geoLocationManager from '@ohos.geoLocationManager';
import BusinessError from "@ohos.base";
try {
let location = geoLocationManager.getLastLocation();
} catch (err) {
console.error("errCode:" + err.code + ",errMessage:" + err.message);
console.error("errCode:" + (err as BusinessError.BusinessError).code + ",errMessage:" + (err as BusinessError.BusinessError).message);
}
```
......@@ -259,10 +260,11 @@
```ts
import geoLocationManager from '@ohos.geoLocationManager';
import BusinessError from "@ohos.base";
try {
let isAvailable = geoLocationManager.isGeocoderAvailable();
} catch (err) {
console.error("errCode:" + err.code + ",errMessage:" + err.message);
console.error("errCode:" + (err as BusinessError.BusinessError).code + ",errMessage:" + (err as BusinessError.BusinessError).message);
}
```
......@@ -270,7 +272,7 @@
- 调用getAddressesFromLocation,坐标转化地理位置信息。
```ts
let reverseGeocodeRequest = {"latitude": 31.12, "longitude": 121.11, "maxItems": 1};
let reverseGeocodeRequest:geoLocationManager.ReverseGeoCodeRequest = {"latitude": 31.12, "longitude": 121.11, "maxItems": 1};
try {
geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest, (err, data) => {
if (err) {
......@@ -280,7 +282,7 @@
}
});
} catch (err) {
console.error("errCode:" + err.code + ",errMessage:" + err.message);
console.error("errCode:" + (err as BusinessError.BusinessError).code + ",errMessage:" + (err as BusinessError.BusinessError).message);
}
```
......@@ -288,7 +290,7 @@
- 调用getAddressesFromLocationName位置描述转化坐标。
```ts
let geocodeRequest = {"description": "上海市浦东新区xx路xx号", "maxItems": 1};
let geocodeRequest:geoLocationManager.GeoCodeRequest = {"description": "上海市浦东新区xx路xx号", "maxItems": 1};
try {
geoLocationManager.getAddressesFromLocationName(geocodeRequest, (err, data) => {
if (err) {
......@@ -298,7 +300,7 @@
}
});
} catch (err) {
console.error("errCode:" + err.code + ",errMessage:" + err.message);
console.error("errCode:" + (err as BusinessError.BusinessError).code + ",errMessage:" + (err as BusinessError.BusinessError).message);
}
```
......@@ -332,11 +334,12 @@
1. 使用地理围栏功能,需要有权限ohos.permission.APPROXIMATELY_LOCATION,位置权限申请的方法和步骤见[申请位置权限开发指导](#申请位置权限开发指导)
2. 导入[geoLocationManager](../reference/apis/js-apis-geoLocationManager.md)模块[wantAgent](../reference/apis/js-apis-app-ability-wantAgent.md)模块。
2. 导入[geoLocationManager](../reference/apis/js-apis-geoLocationManager.md)模块[wantAgent](../reference/apis/js-apis-app-ability-wantAgent.md)模块和[BusinessError](../reference/apis/js-apis-base.md)模块。
```ts
import geoLocationManager from '@ohos.geoLocationManager';
import wantAgent from '@ohos.app.ability.wantAgent';
import BusinessError from "@ohos.base";
```
3. 创建[WantAgentInfo](../reference/apis/js-apis-inner-wantAgent-wantAgentInfo.md)信息。
......@@ -344,10 +347,10 @@
场景一:创建拉起Ability的WantAgentInfo信息。
```ts
let wantAgentObj = null; // 用于保存创建成功的wantAgent对象,后续使用其完成触发的动作。
let wantAgentObj:wantAgent.WantAgentInfo|null = null; // 用于保存创建成功的wantAgent对象,后续使用其完成触发的动作。
// 通过WantAgentInfo的operationType设置动作类型
let wantAgentInfo = {
let wantAgentInfo:wantAgent.WantAgentInfo = {
wants: [
{
deviceId: '',
......@@ -368,10 +371,10 @@
场景二:创建发布[公共事件](../application-models/common-event-overview.md)的WantAgentInfo信息。
```ts
let wantAgentObj = null; // 用于保存创建成功的WantAgent对象,后续使用其完成触发的动作。
let wantAgentObj:wantAgent.WantAgentInfo|null = null; // 用于保存创建成功的WantAgent对象,后续使用其完成触发的动作。
// 通过WantAgentInfo的operationType设置动作类型
let wantAgentInfo = {
let wantAgentInfo:wantAgent.WantAgentInfo = {
wants: [
{
action: 'event_name', // 设置事件名
......@@ -397,11 +400,11 @@
}
console.info('getWantAgent success');
wantAgentObj = data;
let requestInfo = {'priority': 0x201, 'scenario': 0x301, "geofence": {"latitude": 121, "longitude": 26, "radius": 100, "expiration": 10000}};
let requestInfo:geoLocationManager.GeofenceRequest = {'scenario': 0x301, "geofence": {"latitude": 121, "longitude": 26, "radius": 100, "expiration": 10000}};
try {
geoLocationManager.on('gnssFenceStatusChange', requestInfo, wantAgentObj);
} catch (err) {
console.error("errCode:" + err.code + ",errMessage:" + err.message);
console.error("errCode:" + (err as BusinessError.BusinessError).code + ",errMessage:" + (err as BusinessError.BusinessError).message);
}
});
```
......
......@@ -40,11 +40,11 @@ USB设备可作为Host设备连接Device设备进行数据传输。开发示例
1. 获取设备列表。
```js
```ts
// 导入USB接口api包。
import usb from '@ohos.usbManager';
// 获取设备列表。
let deviceList = usb.getDevices();
let deviceList : Array<USBDevice> = usb.getDevices();
/*
deviceList结构示例
[
......@@ -100,22 +100,25 @@ USB设备可作为Host设备连接Device设备进行数据传输。开发示例
2. 获取设备操作权限。
```js
let deviceName = deviceList[0].name;
```ts
import usb from '@ohos.usbManager';
import { BusinessError } from '@ohos.base';
let deviceName : string = deviceList[0].name;
// 申请操作指定的device的操作权限。
usb.requestRight(deviceName).then(hasRight => {
usb.requestRight(deviceName).then((hasRight : boolean) => {
console.info("usb device request right result: " + hasRight);
}).catch(error => {
}).catch((error : BusinessError)=> {
console.info("usb device request right failed : " + error);
});
```
3. 打开Device设备。
```js
```ts
// 打开设备,获取数据传输通道。
let pipe = usb.connectDevice(deviceList[0]);
let interface1 = deviceList[0].configs[0].interfaces[0];
let pipe : USBDevicePipe = usb.connectDevice(deviceList[0]);
let interface1 : number = deviceList[0].configs[0].interfaces[0];
/*
打开对应接口,在设备信息(deviceList)中选取对应的interface。
interface1为设备配置中的一个接口。
......@@ -125,38 +128,40 @@ USB设备可作为Host设备连接Device设备进行数据传输。开发示例
4. 数据传输。
```js
```ts
import usb from '@ohos.usbManager';
import { BusinessError } from '@ohos.base';
/*
读取数据,在device信息中选取对应数据接收的endpoint来做数据传输
(endpoint.direction == 0x80);dataUint8Array是要读取的数据,类型为Uint8Array。
*/
let inEndpoint = interface1.endpoints[2];
let outEndpoint = interface1.endpoints[1];
let dataUint8Array = new Uint8Array(1024);
usb.bulkTransfer(pipe, inEndpoint, dataUint8Array, 15000).then(dataLength => {
let inEndpoint : USBEndpoint = interface1.endpoints[2];
let outEndpoint : USBEndpoint = interface1.endpoints[1];
let dataUint8Array : Array<number> = new Uint8Array(1024);
usb.bulkTransfer(pipe, inEndpoint, dataUint8Array, 15000).then((dataLength : number) => {
if (dataLength >= 0) {
console.info("usb readData result Length : " + dataLength);
} else {
console.info("usb readData failed : " + dataLength);
}
}).catch(error => {
}).catch((error : BusinessError) => {
console.info("usb readData error : " + JSON.stringify(error));
});
// 发送数据,在device信息中选取对应数据发送的endpoint来做数据传输。(endpoint.direction == 0)
usb.bulkTransfer(pipe, outEndpoint, dataUint8Array, 15000).then(dataLength => {
usb.bulkTransfer(pipe, outEndpoint, dataUint8Array, 15000).then((dataLength : number) => {
if (dataLength >= 0) {
console.info("usb writeData result write length : " + dataLength);
} else {
console.info("writeData failed");
}
}).catch(error => {
}).catch((error : BusinessError) => {
console.info("usb writeData error : " + JSON.stringify(error));
});
```
5. 释放接口,关闭设备。
```js
```ts
usb.releaseInterface(pipe, interface1);
usb.closePipe(pipe);
```
......
......@@ -48,12 +48,12 @@ import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
import buffer from '@ohos.buffer';
createFile() {
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 新建并打开文件
function createFile() {
// 新建并打开文件
let file = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
// 写入一段内容至文件
let writeLen = fs.writeSync(file.fd, "Try to write str.");
......@@ -77,11 +77,11 @@ createFile() {
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
readWriteFile() {
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
function readWriteFile() {
// 打开文件
let srcFile = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE);
let destFile = fs.openSync(filesDir + '/destFile.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
......@@ -114,11 +114,11 @@ readWriteFile() {
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
async readWriteFileWithStream() {
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
async readWriteFileWithStream() {
// 打开文件流
let inputStream = fs.createStreamSync(filesDir + '/test.txt', 'r+');
let outputStream = fs.createStreamSync(filesDir + '/destFile.txt', "w+");
......@@ -148,7 +148,6 @@ async readWriteFileWithStream() {
以下示例代码演示了如何查看文件列表:
```ts
// 查看文件列表
import fs, { Filter } from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
......@@ -157,7 +156,7 @@ let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 查看文件列表
getListFile() {
function getListFile() {
let filter: Filter = {
suffix: ['.png', '.jpg', '.txt'], // 匹配文件后缀名为'.png','.jpg','.txt'
displayName: ['test%'], // 匹配文件全名以'test'开头
......
......@@ -33,16 +33,24 @@
调用`backup.getLocalCapabilities()`获取能力文件。
```ts
import backup from '@ohos.file.backup';
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
async function getLocalCapabilities() {
try {
let fileData = await backup.getLocalCapabilities();
console.info('getLocalCapabilities success');
let fpath = await globalThis.context.filesDir + '/localCapabilities.json';
let fpath = filesDir + '/localCapabilities.json';
fs.copyFileSync(fileData.fd, fpath);
fs.closeSync(fileData.fd);
} catch (err) {
console.error('getLocalCapabilities failed with err: ' + err);
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error('getLocalCapabilities failed with err: ' + JSON.stringify(err));
}
}
```
......@@ -88,19 +96,21 @@
**示例**
```ts
import fs from '@ohos.file.fs';
import backup from '@ohos.file.backup';
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';
// 获取沙箱路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 创建SessionBackup类的实例用于备份数据
let g_session: backup.SessionBackup;
createSessionBackup() {
function createSessionBackup() {
let sessionBackup = new backup.SessionBackup({
onFileReady: (err: BusinessError, file: backup.File) => {
if (err) {
console.info('onFileReady err: ' + err);
console.info('onFileReady err: ' + JSON.stringify(err));
}
try {
let bundlePath = filesDir + '/' + file.bundleName;
......@@ -116,21 +126,21 @@
},
onBundleBegin: (err: BusinessError, bundleName: string) => {
if (err) {
console.info('onBundleBegin err: ' + err);
console.info('onBundleBegin err: ' + JSON.stringify(err));
} else {
console.info('onBundleBegin bundleName: ' + bundleName);
}
},
onBundleEnd: (err: BusinessError, bundleName: string) => {
if (err) {
console.info('onBundleEnd err: ' + err);
console.info('onBundleEnd err: ' + JSON.stringify(err));
} else {
console.info('onBundleEnd bundleName: ' + bundleName);
}
},
onAllBundlesEnd: (err: BusinessError) => {
if (err) {
console.info('onAllBundlesEnd err: ' + err);
console.info('onAllBundlesEnd err: ' + JSON.stringify(err));
} else {
console.info('onAllBundlesEnd');
}
......@@ -141,8 +151,8 @@
});
return sessionBackup;
}
async sessionBackup () {
async function sessionBackup () {
g_session = createSessionBackup();
// 此处可根据backup.getLocalCapabilities()提供的能力文件,选择需要备份的应用
// 也可直接根据应用包名称进行备份
......@@ -165,20 +175,22 @@
**示例**
```ts
import backup from '@ohos.file.backup';
import fs from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';
// 创建SessionRestore类的实例用于恢复数据
let g_session: backup.SessionRestore;
async publishFile(file: backup.File) {
async function publishFile(file: backup.File) {
await g_session.publishFile({
bundleName: file.bundleName,
uri: file.uri
});
}
createSessionRestore() {
function createSessionRestore() {
let sessionRestore = new backup.SessionRestore({
onFileReady: (err: BusinessError, file: backup.File) => {
if (err) {
console.info('onFileReady err: ' + err);
console.info('onFileReady err: ' + JSON.stringify(err));
}
// 此处开发者请根据实际场景待恢复文件存放位置进行调整 bundlePath
let bundlePath: string;
......@@ -193,19 +205,19 @@
},
onBundleBegin: (err: BusinessError, bundleName: string) => {
if (err) {
console.error('onBundleBegin failed with err: ' + err);
console.error('onBundleBegin failed with err: ' + JSON.stringify(err));
}
console.info('onBundleBegin success');
},
onBundleEnd: (err: BusinessError, bundleName: string) => {
if (err) {
console.error('onBundleEnd failed with err: ' + err);
console.error('onBundleEnd failed with err: ' + JSON.stringify(err));
}
console.info('onBundleEnd success');
},
onAllBundlesEnd: (err: BusinessError) => {
if (err) {
console.error('onAllBundlesEnd failed with err: ' + err);
console.error('onAllBundlesEnd failed with err: ' + JSON.stringify(err));
}
console.info('onAllBundlesEnd success');
},
......@@ -215,16 +227,16 @@
});
return sessionRestore;
}
async restore01 () {
async function restore01 () {
g_session = createSessionRestore();
const backupApps: string[] = [
const restoreApps: string[] = [
"com.example.hiworld",
]
// 能力文件的获取方式可以根据开发者实际场景进行调整。此处仅为请求示例
// 开发者也可以根据能力文件内容的结构示例,自行构造能力文件内容
let fileData = await backup.getLocalCapabilities();
await g_session.appendBundles(fileData.fd, backupApps);
await g_session.appendBundles(fileData.fd, restoreApps);
console.info('appendBundles success');
// 添加需要恢复的应用成功后,请根据需要恢复的应用名称,调用getFileHandle接口获取待恢复应用数文件的文件句柄
// 应用待恢复数据文件数请依据实际备份文件个数为准,此处仅为请求示例
......@@ -251,21 +263,28 @@
**示例**
```ts
import backup from '@ohos.file.backup';
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';
// 获取沙箱路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 创建SessionRestore类的实例用于恢复数据
let g_session: backup.SessionRestore;
async publishFile(file) {
async function publishFile(file: backup.File) {
await g_session.publishFile({
bundleName: file.bundleName,
uri: file.uri
});
}
createSessionRestore() {
function createSessionRestore() {
let sessionRestore = new backup.SessionRestore({
onFileReady: (err: BusinessError, file: backup.File) => {
if (err) {
console.info('onFileReady err: ' + err);
}
if (err) {
console.info('onFileReady err: ' + JSON.stringify(err));
}
let bundlePath: string;
if( file.uri == "/data/storage/el2/restore/bundle.hap" )
{
......@@ -284,19 +303,19 @@
},
onBundleBegin: (err: BusinessError, bundleName: string) => {
if (err) {
console.error('onBundleBegin failed with err: ' + err);
console.error('onBundleBegin failed with err: ' + JSON.stringify(err));
}
console.info('onBundleBegin success');
},
onBundleEnd: (err: BusinessError, bundleName: string) => {
if (err) {
console.error('onBundleEnd failed with err: ' + err);
console.error('onBundleEnd failed with err: ' + JSON.stringify(err));
}
console.info('onBundleEnd success');
},
onAllBundlesEnd: (err: BusinessError) => {
if (err) {
console.error('onAllBundlesEnd failed with err: ' + err);
console.error('onAllBundlesEnd failed with err: ' + JSON.stringify(err));
}
console.info('onAllBundlesEnd success');
},
......@@ -306,28 +325,28 @@
});
return sessionRestore;
}
async restore02 () {
async function restore02 () {
g_session = createSessionRestore();
const backupApps: string[] = [
const restoreApps: string[] = [
"com.example.hiworld",
]
let fpath = await globalThis.context.filesDir + '/localCapabilities.json';
let file = fs.openSync(fpath, fileIO.OpenMode.CREATE | fileIO.OpenMode.READ_WRITE);
let fpath = filesDir + '/localCapabilities.json';
let file = fs.openSync(fpath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
let content = "{\"bundleInfos\" :[{\"allToBackup\" : false,\"extensionName\" : \"\"," +
"\"name\" : \"cn.openharmony.inputmethodchoosedialog\",\"needToInstall\" : true,\"spaceOccupied\" : 0," +
"\"versionCode\" : 1000000,\"versionName\" : \"1.0.0\"}],\"deviceType\" : \"default\",\"systemFullName\" : \"OpenHarmony-4.0.6.2(Canary1)\"}";
fs.writeSync(file.fd, content);
fs.fsyncSync(file.fd);
await g_session.appendBundles(file.fd, backupApps);
await g_session.appendBundles(file.fd, restoreApps);
console.info('appendBundles success');
// 开发者需要请求安装应用的文件句柄
await g_session.getFileHandle({
bundleName: restoreApps[0],
uri: "/data/storage/el2/restore/bundle.hap"
});
await g_session.getFileHandle({
bundleName: restoreApps[0],
uri: "manage.json"
......
......@@ -31,16 +31,19 @@ fs.writeSync(file.fd, 'upload file test');
fs.closeSync(file);
// 上传任务配置项
let header = new Map<Object, string>();
header.set('key1', 'value1');
header.set('key2', 'value2');
let files: Array<request.File> = [
{ filename: 'test.txt', name: 'test', uri: 'internal://cache/test.txt', type: 'txt' }
]
let data: request.RequestData = { name: 'name', value: 'value' };
let data: Array<request.RequestData> = [{ name: 'name', value: 'value' }];
let uploadConfig: request.UploadConfig = {
url: 'https://xxx',
header: { key1: 'value1', key2: 'value2' },
header: header,
method: 'POST',
files: files,
data: [ data ]
data: data
}
// 将本地应用文件上传至网络服务器
......@@ -56,7 +59,8 @@ try {
.catch((err: BusinessError) => {
console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
})
} catch (err) {
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
}
```
......@@ -80,6 +84,7 @@ import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import request from '@ohos.request';
import { BusinessError } from '@ohos.base';
import buffer from '@ohos.buffer';
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
......@@ -102,7 +107,8 @@ try {
}).catch((err: BusinessError) => {
console.error(`Invoke downloadTask failed, code is ${err.code}, message is ${err.message}`);
});
} catch (err) {
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Invoke downloadFile failed, code is ${err.code}, message is ${err.message}`);
}
```
......@@ -23,6 +23,7 @@ OpenHarmony预置了FileManager文件管理器。系统应用开发者也可以
import fileAccess from '@ohos.file.fileAccess';
import fileExtensionInfo from '@ohos.file.fileExtensionInfo';
import { Filter } from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
```
其中fileAccess提供了文件基础操作的API,fileExtensionInfo提供了应用开发的关键结构体。
......@@ -33,11 +34,14 @@ OpenHarmony预置了FileManager文件管理器。系统应用开发者也可以
在文件访问框架中,使用RootInfo用于表示设备的属性信息。以下示例可以获取所有设备的RootInfo。
```ts
// 获取应用上下文
let context = getContext(this) as common.UIAbilityContext;
// 创建连接系统内所有文件管理服务端的helper对象
let fileAccessHelperAllServer: fileAccess.FileAccessHelper;
createFileAccessHelper() {
function createFileAccessHelper() {
try { // this.context是EntryAbility传过来的Context
fileAccessHelperAllServer = fileAccess.createFileAccessHelper(this.context);
fileAccessHelperAllServer = fileAccess.createFileAccessHelper(context);
if (!fileAccessHelperAllServer) {
console.error("createFileAccessHelper interface returns an undefined object");
}
......@@ -45,7 +49,7 @@ OpenHarmony预置了FileManager文件管理器。系统应用开发者也可以
console.error("createFileAccessHelper failed, errCode:" + error.code + ", errMessage:" + error.message);
}
}
async getRoots() {
async function getRoots() {
let rootIterator: fileAccess.RootIterator;
let rootInfos: fileAccess.RootInfo[] = [];
let isDone: boolean = false;
......@@ -62,7 +66,8 @@ OpenHarmony预置了FileManager文件管理器。系统应用开发者也可以
if (!isDone)
rootInfos.push(result.value);
}
} catch (error) {
} catch (err) {
let error: BusinessError = err as BusinessError;
console.error("getRoots failed, errCode:" + error.code + ", errMessage:" + error.message);
}
}
......@@ -93,7 +98,8 @@ OpenHarmony预置了FileManager文件管理器。系统应用开发者也可以
if (!isDone)
fileInfos.push(result.value);
}
} catch (error) {
} catch (err) {
let error: BusinessError = err as BusinessError;
console.error("listFile failed, errCode:" + error.code + ", errMessage:" + error.message);
}
......@@ -116,7 +122,8 @@ OpenHarmony预置了FileManager文件管理器。系统应用开发者也可以
if (!isDone)
subFileInfos.push(result.value);
}
} catch (error) {
} catch (err) {
let error: BusinessError = err as BusinessError;
console.error("listFile failed, errCode:" + error.code + ", errMessage:" + error.message);
}
```
......@@ -140,7 +147,8 @@ OpenHarmony预置了FileManager文件管理器。系统应用开发者也可以
return;
}
console.info("createFile sucess, fileUri: " + JSON.stringify(fileUri));
} catch (error) {
} catch (err) {
let error: BusinessError = err as BusinessError;
console.error("createFile failed, errCode:" + error.code + ", errMessage:" + error.message);
};
```
......@@ -29,7 +29,8 @@
fs.writeSync(file.fd, 'content');
// 关闭文件
fs.closeSync(file.fd);
} catch (err) {
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Failed to openSync / writeSync / closeSync. Code: ${err.code}, message: ${err.message}`);
}
```
......@@ -58,7 +59,8 @@
// 打印读取到的文件数据
let buf = buffer.from(arrayBuffer, 0, num);
console.info('read result: ' + buf.toString());
} catch (err) {
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Failed to openSync / readSync. Code: ${err.code}, message: ${err.message}`);
}
```
......@@ -78,7 +78,7 @@
let volId: string = data.parameters.id;
volumeManager.getVolumeById(volId, (error: BusinessError, vol: volumeManager.Volume) => {
if (error) {
console.error('volumeManager getVolumeById failed');
console.error('volumeManager getVolumeById failed for ' + JSON.stringify(error));
} else {
console.info('volumeManager getVolumeById successfully, the volume state is ' + vol.state);
}
......
......@@ -30,7 +30,7 @@ save接口会将文件保存在文件管理器,而不是图库。
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
let pixelmapArrayBuffer: ArrayBuffer;
async getPixelmap() {
async function getPixelmap() {
try {
let predicates = new dataSharePredicates.DataSharePredicates();
let fetchOption: photoAccessHelper.FetchOptions = {
......@@ -48,10 +48,11 @@ save接口会将文件保存在文件管理器,而不是图库。
pixelmapArrayBuffer = readBuffer;
})
}).catch((err: BusinessError) => {
console.error('[picker] getThumbnail failed with error: ' + err);
console.error('[picker] getThumbnail failed with error: ' + JSON.stringify(err));
});
} catch (error) {
console.error('[picker] getThumbnail error = ' + error);
let err: BusinessError = error as BusinessError;
console.error('[picker] getThumbnail error = ' + JSON.stringify(err));
}
}
```
......@@ -74,11 +75,13 @@ save接口会将文件保存在文件管理器,而不是图库。
uris = photoSaveResult;
console.info('photoViewPicker.save to file succeed and uris are:' + uris);
}
} catch (err) {
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`[picker] Invoke photoViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
}
} catch (error) {
console.info("[picker] photoViewPickerSave error = " + error);
let err: BusinessError = error as BusinessError;
console.info("[picker] photoViewPickerSave error = " + JSON.stringify(err));
}
}
```
......@@ -88,14 +91,15 @@ save接口会将文件保存在文件管理器,而不是图库。
然后,通过fd使用[fs.write](../reference/apis/js-apis-file-fs.md#fswrite)接口对这个文件进行编辑修改,编辑修改完成后关闭fd。
```ts
async writeOnly(uri: string) {
async function writeOnly(uri: string) {
try {
let file = fs.openSync(uri, fs.OpenMode.WRITE_ONLY);
let writeLen = await fs.write(file.fd, pixelmapArrayBuffer);
fs.closeSync(file);
console.info("[picker] writeOnly writeLen = " + writeLen);
} catch (error) {
console.info("[picker] writeOnly error: " + error);
let err: BusinessError = error as BusinessError;
console.info("[picker] writeOnly error: " + JSON.stringify(err));
}
}
```
......
......@@ -124,6 +124,7 @@
// 获取到文档文件的文件名称
let file_name_list = result.want.parameters.file_name_list;
} catch (err) {
let err: BusinessError = error as BusinessError;
console.error(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
}
```
......
......@@ -138,11 +138,13 @@ function getShareFile() {
// 根据需要对被分享文件的URI进行相应操作。例如读写的方式打开URI获取file对象
let file = fs.openSync(uri, fs.OpenMode.READ_WRITE);
console.info('open file successfully!');
} catch (error) {
} catch (err) {
let error: BusinessError = err as BusinessError;
console.error(`Invoke openSync failed, code is ${error.code}, message is ${error.message}`);
}
} catch (error) {
console.error(`Invoke openSync failed, code is ${error.code}, message is ${error.message}`);
let err: BusinessError = error as BusinessError;
console.error(`Invoke openSync failed, code is ${err.code}, message is ${err.message}`);
}
}
```
......@@ -3,31 +3,7 @@
- [前言](foreword.md)
- [简介](introduction.md)
- [从一个例子开始](start-with-a-example.md)
- 应用UX设计
- [设计原则和要点](design-principles.md)
- 应用架构设计
- [应用导航结构设计要求](navigation-design.md)
- [应用页面结构设计](page-design.md)
- 界面布局
- [概述](interface-layout-design-intro.md)
- 布局基础
- [栅格系统](design-grid.md)
- [自适应布局](design-adaptive-layout.md)
- [响应式布局](design-responsive-layout.md)
- [布局基础运用案例](design-layout-cases.md)
- 人机交互
- [交互基础](interaction-basics.md)
- [常见输入方式](common-input-modes.md)
- [交互事件归一](design-interaction-event-normalization.md)
- 视觉风格
- [视觉基础](visual-basics.md)
- [色彩](visual-style-color.md)
- [字体](visual-style-font.md)
- [图标](visual-style-icon.md)
- [多态控件](design-polymorphic-controls.md)
- [设计自检表](design-checklist.md)
- [设计交付](design-delivery.md)
- [资源](design-resources.md)
- [应用UX设计](design-principles.md)
- [工程管理](ide-using.md)
- 页面开发的一多能力介绍
- [简介](page-development-intro.md)
......
# 常见输入方式
## 输入类型
**基于触控的交互**
很多设备的屏幕都支持多点触控,允许用户通过手指或手写笔进行交互。它们与屏幕的接触状态、数量以及运动行为被识别成触控手势和操作,可以支持多种交互功能和体验(例如点击、滑动、缩放、旋转)。在多数情况下,应将触控交互作为用户首要的交互方式。
以下是基础的手势操作:
| **手势操作** | **功能描述** | **图示** |
| -------- | -------- | -------- |
| 点击 | 用户通过点击某个元素触发功能或访问界面。 | ![zh-cn_image_0000001291916533](figures/zh-cn_image_0000001291916533.png) |
| 长按 | 用户通过长按某个元素触发菜单或特定模式或进入界面。长按手势发现性差,常用功能不要使用长按来触发。<br/>场景:<br/>-&nbsp;长按操作显示弹出菜单。例如,在联系人列表中长按某个联系人。<br/>-&nbsp;长按操作显示快捷菜单。例如,长按某个桌面图标。<br/>-&nbsp;长按操作进入多选。一般用于列表或宫格界面。例如,长按信息的列表界面,长按图库的宫格界面。<br/>-&nbsp;长按操作进入编辑模式或可排序模式,并伴随振动。例如,长按闹钟的列表界面,长按桌面的空白处。<br/>-&nbsp;长按操作选择文本。例如,在文本框中长按已经输入的文字。<br/>-&nbsp;长按图标进入该图标对应的功能详情。例如,在通知面板上长按功能开关图标。 | ![zh-cn_image_0000001245596752](figures/zh-cn_image_0000001245596752.png) |
| 滑动 | 用户通过滑动来滚动列表或平移界面内容。<br/>场景:<br/>-&nbsp;通过滑动滚动列表。<br/>-&nbsp;在内容区横向滑动切换页签。<br/>-&nbsp;通过滑动平移地图。 | ![zh-cn_image_0000001245437476](figures/zh-cn_image_0000001245437476.png) |
| 拖动 | 用户将元素从一个位置移动到另外一个位置。 | ![zh-cn_image_0000001291557665](figures/zh-cn_image_0000001291557665.png) |
| 双击 | 用户快速点击两下以放大/缩小内容、选择文字或触发特定的功能。 | ![zh-cn_image_0000001245277712](figures/zh-cn_image_0000001245277712.png) |
| 捏合 | 用户使用两个手指按住屏幕向外展开以放大内容。<br/>使用两个手指按住屏幕向内收拢以缩小内容。<br/>场景:<br/>-&nbsp;放大/缩小图片。例如,在查看图片界面。<br/>-&nbsp;放大/缩小内容。例如,浏览页面或者相机取景界面。 | ![zh-cn_image_0000001291677093](figures/zh-cn_image_0000001291677093.png) |
**基于光标的交互**
当用户使用指向设备(鼠标、触摸板、AR/VR手柄、隔空手势等)与应用程序进行间接交互时,光标指向的对象和光标本身应提供适当的视觉反馈以表达对象的可交互性和到达的准确性。同时,应考虑利用光标支持精细化操作和悬浮状态的特性(相比手指触摸),以提升应用生产力、简化交互任务和增强信息展示。
基于光标的交互设计亦可推广至AR/VR手柄和隔空手势等空间交互场景。
**基于焦点的交互**
当用户使用键盘、智慧屏遥控器、车机摇杆/旋钮等非指向性输入方式与应用程序进行间接交互时,基于焦点的导航和交互是重要的输入手段。
## 典型输入方式
**鼠标**
鼠标是一种典型的基于光标的、具备像素级精度的指向型输入方式,最为适用于对用户交互具有较高精度要求的生产力应用和高密度UI的场景。
一般地,鼠标由左键、右键和滚轮键组成,这些按键的交互应遵循业界标准的规范功能和用户的既有使用习惯。鼠标也可以通过和不同的键盘按键进行结合,提供额外的快捷操作体验。
**触控板**
触控板同时具备多指触控手势输入(触屏)和精细化指向型输入(鼠标)的特性,使得触控板既适合用于基于触摸交互优化的用户界面,也适合用于对指点精度有较高要求的生产力应用。
**键盘**
键盘是一种重要的生产力输入方式,优秀的键盘使用体验应允许用户快速准确地进行文本输入、双手无需离开键盘即可在系统和应用内进行导航、访问所有的功能、以及支持无障碍体验。
**手写笔**
在触屏上,手写笔是手指精细化操作的延伸,是一种像素级精度的指点设备。手写笔提供了一种直接的、自然的方式来进行数字内容书写、绘图和标注。
**隔空手势**
隔空手势(非接触手势)是一种人与设备交互的新方式,用户可以在无需手持或接触设备的情况下与设备进行便捷的交互。随着技术的发展,隔空手势在默认设备、平板、车机、智慧屏、音箱、AR/VR等设备上都有一定的应用。通常情况下,隔空手势通常是符合用户直觉、文化习惯或者容易操作的动作。
# 自适应布局
自适应布局是通过设定元素与外部容器的相对关系实现的。当外部容器大小、位置等发生变化时,元素即可以根据相对关系自动变化以适应外部环境的变化。通常自适应布局能根据vp/px变化进行无级连续的变化。
## 自适应拉伸
某单个内容或某组内容的显示宽度不是固定值,而是通过相对参照物的方式来确定其显示宽度。当参照物的宽度发生变化时,内容或内容间距的宽度随之发生自适应拉伸。
左右拉伸:例如,列表开关组合中,在窗口宽度变化时,开关控件固定宽度并相对列表的右边缘位置固定,整个组合与文本宽度均自适应变化。
![拉伸能力](figures/拉伸能力.gif)
均分拉伸:例如,在图标型网格中,当窗口宽度变化时,入口图标间距与图标离左右边缘间距同时均等变化。
![均分能力](figures/均分能力.gif)
自适应拉伸适用于文字、普通按钮、间距等展示宽度灵活,对宽高比不敏感的内容和内容组合。
当可能出现的拉伸宽度不足以显示默认内容时,应根据场景选择优先保证内容完整或者优先保证其他内容的屏效,并进行截断或换行等组合适配。
![拉伸注意场景](figures/拉伸注意场景.png)
## 自适应缩放
组件的显示大小是固定比例,通过相对参照物的方式来确定其宽或高。当参照物的大小发生变化时,元素的大小随之发生自适应缩放。
完整缩放:例如,在宽度或高度变化时,时钟始终保证表盘完整展示并根据较短边决定宽高。
![zh-cn_image_0000001291675753](figures/zh-cn_image_0000001291675753.gif)
占比缩放:例如,带主体和背景的插画,画面内容根据宽度变化裁切,根据高度变化按50%比例缩放。
![缩放案例](figures/缩放案例.gif)
自适应缩放适用于图片、圆形按钮、banner、反应真实物体形状的图像等必须保证宽高比的内容。
不推荐将所有元素同时缩放、或某内容放大过大超过屏幕50%。这将导致获取信息量不增反减,不符合用户预期。
![4.3-2](figures/4.3-2.png)
## 自适应延伸
组件的显示数量不是固定的,而是通过相对参照物的方式来确定其显示数量。当参照物的宽度发生变化时,组件随之发生自适应延伸显示更多数量。
同功能内容延伸:例如,子页签和可滑动宫格在默认宽度下通过露出最后内容,提示右方有更多入口,在宽度变化时,可在每个元素宽度不变、保持滑动交互时显示更多数量。
![延长能力](figures/延长能力.gif)
不同功能内容延伸或隐藏:例如,默认处于同一排的不同音乐播放按钮优先级不同,在宽度变化时可延伸或隐藏低优先级的按钮,最大化适应不同窗口尺寸。
![隐藏能力](figures/隐藏能力.gif)
自适应延伸/隐藏适用于页签、操作块、推荐栏目等具有相同交互层级且有更多数据可以填充的内容。
注意:需要判断因隐藏而不展示的内容对功能完整性是否有影响,并考虑通过滑动或“更多”按钮提供查看使用该内容的方式。
## 自适应折行
定义了折行能力的组件,可以根据组件容器的可用空间,体现纵向布局或者横向布局。
例如,在宽度足够时,操作块位于同一行,在宽度变小时,可根据内容能显示的宽度纵向排布。
![折行案例分镜](figures/折行案例分镜.png)
自适应折行适用于页签、操作块、内容流等具有相同交互层级,且希望保证类型和数量完整性的内容。
自适应布局对应OpenHarmony系统提供的自适应布局能力中的拉伸、均分、缩放、占比、延伸、隐藏、折行。自适应布局能力详见本文“[自适应布局](adaptive-layout.md)”相关介绍。
# 设计自检表
设计自检表详细列举出了在全场景设备设计和开发过程中应当注意的设计规则,这将帮助应用减少用户舆情且提升用户体验的一致性。
自检表的要求范围分为“必须”与“推荐”两个类别。必须类一般为已总结出的较优解决方案或效果,表示相关设计需要按照此原则统一执行;推荐类指可能受应用品牌风格或业务特殊性影响,可适量做出修改。
请参考以下表格范围内提出的要求对应用进行检查。
| **类型** | **条目** | **说明** | **类别** |
| -------- | ------------------ | ------------------------------------------------------------ | ------ |
| 应用架构 | 导航结构 | 在各设备上页面导航结构保持一致(同时出多个设备的UX设计)。 | 推荐 |
| 布局 | 拉通设计 | 拉通各设备的布局设计,保证在不同尺寸和分辨率的设备上能够无错位/不截断/不变形/不过多空白(50%以上)/不过于拥挤(间距小于16vp,明显截断)/无大图大字体地正常显示。 | 必须 |
| | 响应式设计 | 栅格布局只能占N列以及N列内部的Gutter,不包含N列两侧的Gutter。 | 必须 |
| | 响应式设计 | 明确标注使用什么类型的栅格、给出在不同断点下栅格三要素取值。 | 推荐 |
| | 响应式设计 | 按容器去对齐栅格,而不是内部子元素对齐栅格。 | 必须 |
| | 响应式设计 | 栅格除了页面布局设计外,在做局部栅格设计时,需要通过明显方式如颜色等进行标注区分,避免混淆。 | 推荐 |
| | 响应式设计 | 禁止出现标注了栅格但实际没有通过栅格进行布局设计,避免混淆。 | 必须 |
| | 自适应设计 | 非栅格设计场景下,明确标注自适应布局能力。自适应布局能力有:拉伸、均分、占比、缩放、延伸、隐藏、折行。 | 推荐 |
| 人机交互 | 输入方式 | 需保证在各设备上完整支持触摸、鼠标、触控、键盘、遥控器、摇杆等交互方式,并符合标准定义。 | 推荐 |
| | 交互归一 | 应使用系统提供的控件以达到一致的交互体验。如有定制,需保证在各场景下,不同输入设备上的操作与指南要求一致。需特别注意鼠标行为。 | 推荐 |
| 视觉风格 | 单位 | 用于界面布局的单位应全部使用vp。只针对严格控制元素尺寸的场景使用px。 | 必须 |
| | 色彩 | 用于色彩的赋值应使用分层参数。推荐支持深色模式,需保证界面在系统切换色彩模式时没有识别性问题。 | 推荐 |
| | 字体 | 使用fp为文字大小单位,需要响应系统大字体模式,确保系统调节字体大小后,界面字体能响应变化大小,并且界面布局没有出现布局错乱问题。 | 必须 |
| 多态控件 | 支持常用的控件状态 | 确保控件不同状态下的视觉效果没有缺失。控件的常用状态有:正常态、不可用态、点击态、获焦态、激活态、悬停态。 | 推荐 |
\ No newline at end of file
# 设计交付
## 概述
为了将UX设计详尽准确地实现并传递给开发者,设计师需向开发者提供必要的设计交付件,包括但不限于:应用规格说明、交互流程、视觉效果、视觉标注、视觉规格说明、切图资源、动效参数资源、音效资源等内容。
面向多设备多尺寸的应用UX设计交付件,以完整说明界面布局与视觉设计为主要目的,至少应包含:效果图、标注图、规格说明和切图资源。
下面将分别介绍不同交付内容的用途与规范。
![4.8-交付4件套](figures/4.8-交付4件套.png)
## 效果图
效果图是表达UX设计的关键内容,用于直观呈现目标体验的静态效果,并作为开发者与测试工程师的实现与验收依据。效果图一般为jpg或png格式。
在优秀的设计交付件中,针对不同前置条件下,各交互流程所途经的每个界面都应匹配效果图。对于大量重复使用的组件元素,如弹窗和即时反馈,亦可配合规格统一说明,不做过多重复设计。
在多设备设计中,为了清晰表达设计效果,设计师仍需要针对不同尺寸的关键界面提供符合规范的效果图。关键界面包括且不限于:
- 应用的首页
- 可以从首页直接进入的各二级界面
- 核心使用流程途经的各界面
- 能体现多数界面的自适应与响应式规格的典型界面
不同尺寸的效果图,至少应包含小设备、中设备、大设备。根据业务涉及的设备特点,可补充超小尺寸效果,及不同设备的横竖屏效果,提前充分验证并优化UX设计体现的差异性、一致性、灵活性、兼容性。
对应不同设备效果图的设计画板尺寸推荐如下:
| 设备类型 | 屏幕宽度 | 画板尺寸(vp) |
| -------- | -------- | -------- |
| 超小设备 | [0,&nbsp;320) | 240\*320 |
| 小设备 | [320,&nbsp;600) | 360\*780 |
| 中设备 | [600,&nbsp;840) | 677\*763 |
| 大设备 | [840,&nbsp;+) | 1280\*800 |
多设备效果示意图
![4.8-效果示意图](figures/4.8-效果示意图.png)
> **说明:**
> - 画板尺寸为不同宽度断点下的典型设备屏幕尺寸,可根据业务具体针对的设备选择其他画板尺寸。
>
> - 画板尺寸以vp为单位提供,根据实际设计所需精度,可统一把设计文档中所有画板设置为1倍或多倍的px尺寸。
>
> - 当效果图需要展示一屏以上的内容,如列表内容较长时,建议保持宽度不变并增加画板高度以容纳更多内容。
## 标注图
标注图是向开发者传递界面上每个元素详细属性,以指导代码完整实现UX设计的图像化文档。界面元素的属性包括:色彩、尺寸、字体、圆角、间距、阴影、模糊、缩放、所用的组件、自适应布局、响应式布局等。标注图一般为jpg或png格式。
在优秀的设计交付件中,不同类型属性的标注文本大小一致、色彩不一、互不重叠,并与效果图内容在视觉上良好区隔。标注图数量与关键界面效果图一一对应,如遇到较复杂的界面,为了更清晰说明规格,也有多张标注图对应一张效果图的情况。
随着设计与开发工具的演进,一些业界工具支持界面元素属性的自动识别并创建标注,也可导出支持动态展示标注的效果图文档,一般为html格式。OpenHarmony应用的设计交付同样可以借助这些工具作为标注图的补充内容,但必选的分层参数仍需设计师专门确认并手动完成标注。
除了尺寸、间距等强依赖于业务具体设计的参数,色彩、字体、圆角、阴影、模糊等属性应尽可能使用分层参数完成标注。
标注示意图
![4.8-标注图](figures/4.8-标注图.png)
> **说明:**
> - 如遇到分层参数覆盖不到的属性值,可按照具体设计效果标注。此时必须详细考虑不同场景下该元素的效果。
## 规格说明
规格说明是一份专门编写的文档,用于更完整清晰地说明界面间的通用元素与变化规则,一般为pdf格式。规格说明不是UX设计交付件中的必选项,但它能帮助设计师节省很多重复性工作,帮助开发者快速理解UX设计规格,在面向多设备的应用设计交付中非常推荐。
在多设备设计中,规格说明常用于并排展示同一界面在不同尺寸下的效果,并说明栅格变化规则。
一多规范栅格图
![4.8-栅格标注](figures/4.8-栅格标注.png)
## 切图资源
包含在设计效果中的素材,如图标、图片、序列帧等,应根据实际需要输出为合适的格式提供开发者置入界面内。
为了更好在多设备上根据设备dpi展示清晰的图像,一分优秀的应用应含有多套同名的切图资源,它们分别存放在mdpi、ldpi、xldpi、xxldpi的文件夹目录下,最终会存放到应用资源包的同名路径中。
多套同名切图文件夹示意图
![4.8-切图资源对应文件夹](figures/4.8-切图资源对应文件夹.png)
图标资源可以是png、jpg、webp、svg等格式。推荐在多设备设计中使用svg图标资源,因为能充分利用矢量图片体积较小、可以自由缩放且不出现锯齿、可根据色彩参数实时赋色的特点,仅用一套资源即可满足复杂场景的UX规范。如使用其他位图格式如png、jpg、webp,则需分别交付各dpi下的切图资源,以达到边缘像素清晰的体验。
图片资源可以是png、jpg、webp等格式。与图标资源类似,一般应提供各dpi下的图片资源。在不同dpi下对边缘像素要求不高的图片如背景图,则推荐按照更高dpi提供一张资源复用到各dpi,以减少应用包大小。
在多设备设计中,图片也可根据设计效果,在不同宽度断点使用同一个资源,或分别交付:
- 当图片所在组件接口提供的缩放显示机制满足UX设计效果,可以用同一个资源
- 当图片所在组件接口提供的缩放显示机制不满足、且不可通过简单的自定义规则实现UX效果,则推荐使用不同资源
夜晚单张适配多设备宽度示意图
![4.8-夜晚单张适配多设备](figures/4.8-夜晚单张适配多设备.png)
晴天多张适配多设备宽度示意图
![4.8-晴天多张适配](figures/4.8-晴天多张适配.png)
# 栅格系统
栅格系统是一个多设备下通用的辅助定位系统,适用于应用窗口的整体布局,也支持界面局部内容使用。栅格系统由 Margin,Gutter,Column 三个属性构成。Margin是相对屏幕、窗口等父容器左右边缘的距离,决定了内容可展示的整体宽度;Gutter是每个Column的间距,决定内容间的紧密程度; Column是内容的占位元素,其数量决定了内容的布局复杂度。Margin大小、Gutter大小、Column数量综合决定Column的具体宽度。
通过栅格系统进行布局,可以更好达到多设备下布局的一致性。
![zh-cn_image_0000001224173302](figures/zh-cn_image_0000001224173302.png)
Margin、Gutter的大小、Column的数量均可自定义,界面内容跟据Column的边缘定位。通过采用不同数值调整内容信息量和紧密程度,一般推荐使用4或8的倍数。例如Margin 32vp、Gutter 16vp、Column数量为4,或Margin 40vp、Gutter 24vp、Column数量为8。
![栅格系统例](figures/栅格系统例.png)
栅格系统对应OpenHarmony系统提供的布局能力中的栅格布局,详见本文 “[栅格布局](responsive-layout.md#栅格布局)”相关介绍。
# 交互事件归一
本章节描述了在多种交互任务或场景下,应用在触屏上和其它常用的输入方式(例如鼠标、触摸板、键盘)上分别对应的正确的交互规则。**设计师和开发者应保证在当前输入方式下应用能够以正确的、符合用户习惯的交互规则进行响应。**
![zh-cn_image_0000001224333656](figures/zh-cn_image_0000001224333656.png)
## 打开/切换对象
用户通过点击某个元素触发功能、访问新页面、或改变自身状态。
| **输入方式** | **交互行为** | **示意** |
| -------- | -------- | -------- |
| 触屏 | 单指单击 | ![zh-cn_image_0000001280472681](figures/zh-cn_image_0000001280472681.png) |
| 鼠标 | 左键单击&nbsp;/&nbsp;左键双击 | ![zh-cn_image_0000001236472600](figures/zh-cn_image_0000001236472600.png) |
| 触摸板 | 单指单击&nbsp;/&nbsp;单指双击 | ![zh-cn_image_0000001280232265](figures/zh-cn_image_0000001280232265.png) |
| 键盘 | 移动焦点到对象上后按下Space键 | ![zh-cn_image_0000001280472701](figures/zh-cn_image_0000001280472701.png) |
一般地,触屏手指的按下/抬起行为对应于光标的按下/抬起行为。
在一些特殊场景,可能会存在使用鼠标/触摸板双击打开对象的交互方案,例如打开桌面应用或文件。此类情况需由应用单独特殊处理,且同一功能不能同时支持单击和双击两种交互方式。
## 显示菜单
某个元素上显示弹出菜单或快捷方式菜单。
![zh-cn_image_0000001268533753](figures/zh-cn_image_0000001268533753.jpg)
| **输入方式** | **交互行为** |
| -------- | -------- |
| 触屏 | 单指长按 |
| 鼠标 | 右键单击/左键长按(保留触屏习惯) |
| 触摸板 | 双指轻单击/重单击/单指重长按(保留触屏习惯) |
| 键盘 | (无通用操作) |
这里的菜单指的是广义的菜单,即用于展示用户可执行的操作的临时性弹出窗口。
凡是在触屏上通过长按显示的菜单,都需要支持鼠标右键单击和触摸板双指单击的触发方式。
## 拖拽对象
直接指向某个元素并移动到界面其他位置
![zh-cn_image_0000001268653953](figures/zh-cn_image_0000001268653953.png)
| **输入方式** | **交互行为** |
| -------- | -------- |
| 触屏 | 长按某对象后触发可拖拽状态,然后移动手指改变对象位置。 |
| 鼠标&nbsp;/&nbsp;触摸板 | 鼠标左键或触摸板单指按下即可拖拽对象(无需长按等待)。 |
| 键盘 | (无通用操作) |
# 布局基础运用案例
## 平级导航的复合网格视图
平级导航的复合网格视图常出现在同时展示多种不同内容的界面。
例如,市场类应用作为典型的平级导航,其首页不同板块采用了不同布局能力。
![一多-布局1](figures/一多-布局1.png)
- 标题栏与搜索栏:因元素单一、位置固定在顶部,因此适合采用自适应拉伸,并在大尺寸界面中从纵排变为横排,充分利用顶部区域。
- 运营横幅:在小设备上默认为多张轮播展示,随宽度变化采用自适应缩放,在中尺寸界面通过重复布局变为并排多张。
- 图标型网格:对于数量固定、且子内容重要程度相同的网格,需保证完全展示,可采用均分拉伸。对于数量不限的网格,则采用自适应延伸,在更大宽度上展示更多数量。
- 底部导航栏:导航类控件本身综合了均分和折行,在宽度变化时能占用均等宽度并在足够宽度下并排,当在大尺寸界面中,挪移到左边,使不同页签距离更近、同时符合视觉走向。
在横竖屏切换时,也保持了一致的布局能力,实际上完成了大尺寸和中尺寸的切换。
![一多-布局2](figures/一多-布局2.png)
当界面出现在智慧屏上,虽然同是大尺寸界面,为了符合设备样式和遥控器交互规则,搜索栏转化为图标入口,导航栏挪移到页面上部。
![一多-布局3](figures/一多-布局3.png)
## 层级导航的列表视图
层级导航的列表视图常出现在多类简单信息并列或多入口业务入口的界面。
例如,设置类应用作为典型的层级导航,其列表控件采用自适应拉伸。
![布局基础案例-层级导航-设置](figures/布局基础案例-层级导航-设置.png)
在中尺寸设备中,为避免中间区域空白过大,采用缩进布局,大尺寸设备中,为充分利用横向空间,建议采用栅格系统形成分栏效果,并让列表元素在各自区域保持拉伸。
## 专辑详情页面
专辑详情不限于展示音乐内容,也用于展示视频、短视频、电台、书本等内容类合集。
例如,歌单类界面作为典型的内容垂类页面,其总体分为标题栏、歌单信息、歌单操作、歌单列表、播放栏几个板块。
- 标题栏:采用自适应拉伸。
- 歌单信息:采用自适应缩放,并在中尺寸界面进行缩进处理使内容呈现协调。
- 歌单操作:板块内部采用均分拉伸,在小尺寸设备上利用纵向空间、中尺寸设备上挪移到歌单封面右边。
- 歌单列表:板块内部采用左右拉伸,在中尺寸设备上可与歌单信息使用相同缩进布局。
- 播放栏:固定在界面底部,保持左右拉伸即可。
![页面布局-布局基础案例-歌单详情页面布局能力360-800vp](figures/页面布局-布局基础案例-歌单详情页面布局能力360-800vp.png)
在横竖屏切换时,完成了中尺寸和大尺寸的切换。歌单列表板块进行挪移的同时,内部采用了重复布局。
歌单信息和歌单操作板块因较小宽高比,挪移到上下排布。
![页面布局-布局基础案例-歌单详情页面布局能力800-1280vp](figures/页面布局-布局基础案例-歌单详情页面布局能力800-1280vp.png)
当界面出现在智慧屏上,为了符合沉浸简约的设备信息和遥控器交互规则,将部分歌单信息替代原来标题栏的位置,并取消播放栏。同时歌单列表居左,更方便遥控器选择。
![页面布局-布局基础案例-歌单详情页面布局能力1280-1920vp](figures/页面布局-布局基础案例-歌单详情页面布局能力1280-1920vp.png)
# 多态控件
为了支持多设备,应用需要能够在不同的设备上运行,控件作为应用的基础组成部分,需要支持不同的设备,且在视觉、交互、动效等表现形式上针对设备进行必要的调整,达到最佳体验。因此,同一控件在不同的设备上会呈现出不同的形态,称为多态控件。
![zh-cn_image_0000001268129090](figures/zh-cn_image_0000001268129090.png)
多态控件应该具备以下特点:
- 覆盖默认设备、平板,兼顾智慧屏、车机、智能穿戴等终端。
- 场景一致性。在对应的使用场景下,其交互、视觉、动效要保持一致,在设计上属性参数保持一致或差异化。
- 针对设备做优化。多态控件在不同的设备上的呈现应该是该设备下的最佳效果,因此在保证一致性的同时,还需要针对设备的特点进行优化。
## 控件的状态
- 控件的状态是一种视觉呈现,用于展示控件当前处于何种交互阶段。不同控件的相同状态应该保持一致的视觉风格,且应该清晰可见。
- 应用可能部署在不同设备上供用户使用,有些设备会支持多种输入方式。例如平板可以连接蓝牙键盘和鼠标来做文字编辑工作,此时控件需要同时满足键盘和鼠标交互,需要支持获焦态和悬停态,如果控件没有支持这两种状态,在使用键盘走焦时或鼠标悬停时,控件就无法通过呈现出相应的状态为用户提供正确的视觉引导。OpenHarmony默认提供多种交互方式的控件实现,方便开发者支持多种输入方式和交互归一。
常见的状态类型:
| | | |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001268288974](figures/zh-cn_image_0000001268288974.gif)<br/>**正常态**<br/>表明当前控件可交互。 | ![zh-cn_image_0000001268608890](figures/zh-cn_image_0000001268608890.gif)<br/>**不可用态**<br/>表明当前控件不可交互。一般使用灰显的方式呈现。 | ![zh-cn_image_0000001317208945](figures/zh-cn_image_0000001317208945.gif)<br/>**点击态**<br/>表明当前控件当前处于点击状态。<br/>操作:手指或鼠标按下且未释放。 |
| ![zh-cn_image_0000001317488873](figures/zh-cn_image_0000001317488873.gif)<br/>**获焦态**<br/>表明当前控件处于焦点状态。操作:<br/>-&nbsp;键盘或遥控器通过方向键将焦点从一个控件移动到另外一个控件。<br/>-&nbsp;通过语音操作,使得控件获得焦点。 | ![zh-cn_image_0000001317089061](figures/zh-cn_image_0000001317089061.gif)<br/>**激活态**<br/>表明当前控件处于激活的状态。用于有多个元素可获焦的控件<br/>操作:焦点处在页签控件的某个页签上时,该页签获焦。点击此页签,该页签被激活。 | ![zh-cn_image_0000001317328893](figures/zh-cn_image_0000001317328893.gif)<br/>**悬停态**<br/>表明当前控件处于鼠标悬停的状态。<br/>操作:将鼠标悬停在控件之上。 |
## 弹出框
弹出框是一种模态窗口,在弹出框消失之前,用户无法操作其他界面内容,干扰性比较强。通常用来展示用户当前需要的或用户必须关注的信息或操作,其他情况不建议使用弹出框,可考虑通知等其他非模态窗口。 弹出框的内容通常是不同控件进行组合布局。例如文本(可带格式,如缩进、链接、粗体等)、列表、输入框、网格、图标或图片等,常用于选择或确认信息。
# 设计原则和要点
# 应用UX设计
## 设计原则
当为多种不同的设备开发应用时,有如下设计原则:
**差异性**
充分了解所要支持的设备,包括屏幕尺寸、交互方式、使用场景、用户人群等,对设备的特性进行针对性的设计。
**一致性**
除了要考虑每个设备的特性外,还需要考虑不同设备的共性,并使用通用性设计方法提供既符合设备差异性,又具有跨设备一致性的设计,从而减少用户学习的难度,降低应用开发的成本。
**灵活性**
在硬件能力、交互方式、使用场景类似的设备上,应主要考虑布局位置、内容宽度、横向组件数量的调整,避免出现因横竖屏切换、窗口尺寸变化造成的界面元素不合理空白、模糊、变形、截断的问题。
**兼容性**
在硬件能力、交互方式、使用场景差异较大的设备上,除了考虑布局位置、内容宽度、横向组件数量,还需支持不同的输入方式、考虑功能架构的调整,避免出现输入不识别、功能不可用、使用困难的问题。
## 设计要点
很多用户的多设备体验以默认设备为核心或从默认设备往外延伸,因此,围绕全场景体验的OpenHarmony UX设计,将优先确保用户在不同的设备上获得跟使用默认设备类似的体验,同时充分利用设备的优势使体验最大化。
在进行OpenHarmony的多设备应用设计时,需考虑应用以下内容:
### 自适应应用架构
使用自适应应用架构,可以确保应用在不同终端上,以最佳的导航形式来访问应用。OpenHarmony 在开发SDK上提供了便利,开发者可以通过简单配置轻松完成,无需从0开始构建。
例如:默认设备上的底Tab的结构,在Pad上一般使用侧边Tab来代替,在大屏上则是顶部Tab。
![一多-1-1](figures/一多-1-1.png)
更多应用架构的设计内容,详见:应用架构下的[应用导航结构设计要求](navigation-design.md)[应用页面结构设计](page-design.md)
### 响应式界面布局
应用会在不同的场景下使用,常见的有横竖屏切换、分屏。这些场景会导致界面的尺寸和长宽比例发生变化。因此需要考虑内容的响应式布局,确保在各种场景下都有最佳的显示效果。
OpenHarmony 提供了多种布局能力,开发者通过组合运用使内容布局更符合业务需要与用户预期。
例如:默认设备上的滚动banner,在其他设备上可进行延伸,平板上露出更多banner,大屏上完全显示两张。
![一多-概述-界面布局-banner例图](figures/一多-概述-界面布局-banner例图.png)
在不同类型的设备上,界面的尺寸和比例更为多样,再加上使用上的差异,导致设计上更为复杂。为此,可以考虑使用分栏布局、重复布局、挪移布局、缩进布局,进一步解决内容的显示问题。
例如:默认设备上上下排布的大图与列表,在长宽比例更大的设备上可挪移到左右展示。
![概述-界面布局-歌单详情高保真](figures/概述-界面布局-歌单详情高保真.png)
更多界面布局的设计内容,详见:[界面布局](interface-layout-design-intro.md)
### 交互归一
交互归一描述了在多种交互任务或场景下,应用在触屏上和其它常用的输入方式(例如鼠标、触摸板、键盘)上分别对应的正确的交互规则。设计师和开发者应保证在当前输入方式下应用能够以正确的、符合用户习惯的交互规则进行响应。通常情况下,系统已经做好了这些事情,开发者只需正确调用。如果您的操作比较特别,您需要考虑多端上的交互归一,以确保用户体验的一致。
更多交互归一的设计内容,详见:[人机交互](interaction-basics.md)
### 视觉参数化
通过参数,方便的调整各端的视觉,使得各端具备该设备特有的风格。在OpenHarmony中,边距、圆角、阴影、字体大小等,都可以通过参数来进行调整。
![画板copy](figures/画板copy.png)
更多视觉参数化的设计内容,详见:[视觉风格](visual-basics.md)
### 多态控件
应用在多设备上运行,设备也可在不同交互方式下使用。控件作为应用的基础组成部分,需要支持不同的设备,且在视觉、交互、动效等表现形式上针对设备进行必要的调整,达到最佳体验。因此,同一控件在不同的设备上会呈现出不同的形态,称为多态控件。
OpenHarmony默认提供支持多设备的控件,开发者可以直接使用并对不同状态进行自定义。例如平板可以连接蓝牙键盘和鼠标来做文字编辑工作,此时控件需要同时满足键盘和鼠标交互,需要支持获焦态和悬停态。
### 针对性优化
在上述设计内容以外,在具体设备上,推荐针对性地进行特殊的操作和布局优化,使之符合当前设备的使用习惯。
例如:在以键鼠操作的界面上,为确保用户的使用习惯,需要提供额外的设计。
| **以触控为主** | **以键鼠操作为主** |
| -------- | -------- |
| 下拉刷新 | 界面上提供“刷新”图标或适配F5快捷键 |
| 滑动多选 | 鼠标框选 |
| 下拉关闭 | 界面上提供“关闭”图标 |
| 长按浮起拖拽 | 鼠标直接拖拽 |
应用UX设计:一多的应用UX设计需遵循通用设计规则,应该考虑多设备的“差异性”、“一致性”、“灵活性”和“兼容性。详细规范请参见[应用UX设计原则](../../../design/ux-design/app-ux-design.md)
# 资源
为方便UX设计师以及开发者参考使用,本文特提供:
- [分层参数](visual-basics.md)的场景、id、参数详细对照表,[OpenHarmony_系统资源分层设计表_V1.0.xlsm](OpenHarmony_系统资源分层设计表_V1.0.xlsm)
- 符合规范的[设计交付件](design-delivery.md)样例,[OpenHarmony_天气应用UX设计交付件_V1.0.zip](OpenHarmony_天气应用UX设计交付件_V1.0.zip)
系统资源分层设计表列出了当前OpenHarmony中可用系统资源id及其在不同类型设备上的取值,它由六张子表组成,各个子表的含义如下所示。
| 表格 | 简介 |
| -------- | -------- |
| 应用色彩参数 | 在**应用开发**过程中可以使用的**色彩**相关的系统资源。 |
| 应用圆角参数 | 在**应用开发**过程中可以使用的**圆角**相关的系统资源。 |
| 应用字体参数 | 在**应用开发**过程中可以使用的**字体**相关的系统资源。 |
| 应用间距参数 | 在**应用开发**过程中可以使用的**间距**相关的系统资源。 |
| 服务卡片参数 | 在**服务卡片开发**过程中可以使用的系统资源。 |
| 不透明度数值速查表 | 用于将不透明度在**百分比表示形式****十六进制表示形式**之间快速转换的速查表。 |
> **说明:**
> - 推荐应用相关系统参数仅在应用开发场景中使用,卡片相关系统参数仅在卡片开发场景中使用。
>
> - 同一系统参数在不同类型的设备上有不同的取值,当前仅提供了系统参数在默认设备上的取值,后续会针对不同设备类型做补充。
>
> - 颜色可以用“RGB”或“ARGB”形式表示,采用“RGB”表示的颜色,完全不透明;采用“ARGB”表示的颜色,其不透明度由“A”(Alpha通道)确定。如“\#7FFF0000”代表不透明度为50%的红色,“\#FFFF0000”和“\#FF0000”都表示不透明度为100%(即完全不透明)的红色。
# 响应式布局
当基本的自适应布局无法满足多终端上屏幕的体验要求时,我们需要针对不同终端的屏幕特点,设定容器与栅格的关系达到响应式的布局。通常响应式布局能根据栅格断点变化进行有级变化。
## 栅格断点系统
根据设备的水平宽度,OpenHarmony提供了断点系统,覆盖超小、小、中、大 四种屏幕类型,并结合栅格系统默认提供了对应 Column 的数量关系。不同的设备根据自身屏幕水平宽度,在不同的断点范围,系统将自动匹配不同数量的栅格。应用也可针对具体界面自定义栅格。
![删格](figures/删格.png)
栅格断点系统与日常使用的设备屏幕类型有一定的对应关系,例如:超小对应智能穿戴设备,小对应默认设备,中对应平板竖屏,大对应智慧屏与平板横屏。设计师可面向希望运行的设备进行所属屏幕类型的适配。
随着智能设备种类的增加,越来越多产品在四种屏幕类型上具备不同的交互能力,如支持触摸的运动相机(小)、仅支持遥杆的手持云台(小-中)、不可移动的智能台灯(中-大)等,需结合具体设备交互进行对应设计,不可一概而论。
## 缩进布局
为了在宽屏上内容显示有更好的效果,在不同宽度的设备上进行不同缩进效果。
![缩进布局](figures/缩进布局.gif)
缩进适用于,因宽度明显变大,内容拉伸以后导致屏幕空白内容超过50%,或文本内容过长(每行大于30字),但没有上下级界面可供同时展示或上下级界面不适合同时显示的场景。
OpenHarmony提供的默认实现为,当栅格为8column或12column时可以响应6column和8column的缩进布局。
## 挪移布局
利用屏幕的宽度优势,将原先的上下布局切换成左右布局。
例如,上下排布的插画和文字,横屏后左右排布。
![挪移布局](figures/挪移布局.gif)
挪移布局适用于横竖屏切换,以及类似的宽高比明显变化(大于200%)同时希望保证内容完整的场景。
## 重复布局
利用屏幕的宽度优势,将相同属性的组件横向并列排布。
![重复布局](figures/重复布局.gif)
重复布局适用于对宽高比敏感的图片和及组合内容,当内容缩放以后导致原图放大超过150%的场景。
OpenHarmony栅格系统提供的分栏实现为,当栅格为8column或12column时可以将默认4栅格的页面整体进行重复布局。
响应式布局对应OpenHarmony系统提供的布局能力中的栅格断点系统和媒体查询,详见本文 “[响应式布局](responsive-layout.md)”相关介绍。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册