HarmonyOS -服务卡片
服务卡片提供了一种界面展示形式,可以将应用的重要信息或操作前置到服务卡片以达到服务直达、减少跳转层级的体验效果。有些类似于创建了一种 “快键方式”,比如下面的卡片流程图:
- 添加基础卡片
右键入口模块按照图示新增卡片
ArkTS卡片创建完成后,工程中会新增如下卡片相关文件:卡片生命周期管理文件(PhoneFormAbility.ets)、卡片页面文件(WidgetCard.ets)和卡片配置文件(form_config.json)
长按应用图标会出现Hello Word 基础卡片,代表卡片添加成功 - 修改卡片UI
卡片的UI可以像一个一般组件一样进行基础布局,很方便就可以做定制修改,打开WidgetCard.ets文件进行修改即可
/*
1. The title.
*/
readonly TITLE: string = '我是一个卡片';
3.定制样式
卡片的样式定制和普通组件的写法没有任何区别,按照实际需要进行UI改写即可
@Entry()
@Component
struct WidgetCard {
fileNameList: string[] = new Array(2).fill('')
build() {
Column() {
Row() {
Text('今日推荐')
.fontColor('#fff')
.fontSize(16)
}
.height(40)
.width('100%')
.justifyContent(FlexAlign.Center)
Row() {
ForEach(
this.fileNameList,
(url: string) => {
Row() {
Image('')
.borderRadius(12)
.width(50)
.aspectRatio(1)
}
.backgroundColor('#eee')
.borderRadius(12)
}
)
}
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
.layoutWeight(1)
.padding({
left: 20,
right: 20
})
.backgroundColor('#fff')
.borderRadius({
topRight: 16,
topLeft: 16
})
.onClick(() => {
})
}
.linearGradient({
angle: '135',
colors: [
['#FD3F8F', '0%'],
['#FF773C', '100%']
]
})
.height('100%')
}
}
点击卡片唤起特定页
基础实现原理
- 卡片组件点击之后通过postCardAction触发router事件并携带参数
- 在应用的UIAbility中接收router事件,解析参数完成跳转
准备落地页
视图features/my/src/main/ets/views/RecommendView.ets
import { router } from '@kit.ArkUI'
import { MkNavBar } from '@mk/basic'
interface HotGoodsType {
id: string
name: string
price: string
picture: string
}
@Entry
@Component
export struct RecommendView {
build() {
Column() {
Column() {
this.topBanner()
this.swiperContainer()
}
.height('80%')
.justifyContent(FlexAlign.Start)
.linearGradient({
angle: 180,
colors: [
['#51E1F8', 0],
['#F5F4F9', 1]
]
})
Row() {
Row() {
Text('换一批看看')
.fontSize(16)
.fontColor('#fff')
}
.width('100%')
.height(46)
.justifyContent(FlexAlign.Center)
.backgroundColor('#00C6C6')
.onClick(() => {
})
}
.width('100%')
.padding({
left: 16,
right: 16
})
.layoutWeight(1)
.backgroundColor('#F5F4F9')
}
}
// 顶部banner
@Builder
topBanner() {
Stack({ alignContent: Alignment.TopStart }) {
Image($r("app.media.ic_public_hot_banner_bg"))
.width('100%')
MkNavBar({
bg: Color.Transparent,
leftClickHandler: () => {
router.replaceUrl({
url: 'pages/Index'
})
}
})
}
}
// 轮播图
swiperController: SwiperController = new SwiperController()
@Builder
swiperContainer() {
Column() {
Swiper(this.swiperController) {
ForEach(
new Array<HotGoodsType>(3).fill({
id: '1001',
name: 'PLAZA STYLE estaa 珍珠款毛绒绒保暖套装【含…',
price: '289',
picture: 'http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-05/6fdcac19-dd44-442c-9212-f7ec3cf3ed18.jpg'
}),
(item: HotGoodsType) => {
Column() {
Image(item.picture)
.width('100%')
Text(item.name)
.margin({
top: 10
})
.fontSize(14)
.fontColor('#434343')
Text(`¥${item.price}`)
.margin({
top: 10
})
.fontSize(20)
.fontColor('#191919')
.fontWeight(700)
}
.padding(30)
.backgroundColor('#fff')
}
)
}
.width(270)
.borderRadius(16)
.indicator(
new DotIndicator()
.itemWidth(6)
.itemHeight(6)
.selectedItemWidth(6)
.selectedItemHeight(6)
.color('#E9E8EC')
.selectedColor('#191919')
)
}
.margin({
top: -60
})
}
}
页面products/phone/src/main/ets/pages/RecommendPage
import { RecommendView } from '@mk/my'
@Entry
@Component
struct RecommendPage {
build() {
Column() {
RecommendView()
}
.width('100%')
.height('100%')
}
}
跳转打开落地页
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { promptAction, window } from '@kit.ArkUI';
import { Log } from '@abner/log';
import { deviceInfo } from '@kit.BasicServicesKit';
import { auth } from '@mk/basic';
export default class EntryAbility extends UIAbility {
// 保存 传递的信息
private selectPage: string = ''
// 保存当前的 WindowStage 对象
private currentWindowStage: window.WindowStage | null = null;
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
if (want.parameters !== undefined) {
let params: Record<string, string> = JSON.parse(JSON.stringify(want.parameters));
this.selectPage = params.targetPage;
Log.info(this.selectPage)
}
Log.info('onCreate-run')
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// hilog.info(DOMAIN_NUMBER, TAG, `onNewWant Want: ${JSON.stringify(want)}`);
// Log.info('onNewWant-run')
if (want.parameters?.params !== undefined) {
let params: Record<string, string> = JSON.parse(JSON.stringify(want.parameters));
this.selectPage = params.targetPage;
}
// 如果 window 对象存在, 人为的调用 onWindowStageCreate 传入 window 对象
if (this.currentWindowStage !== null) {
// 根据设置的页面 拉起对应的 Page
this.onWindowStageCreate(this.currentWindowStage);
}
}
onDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
// const pageName = this.selectPage != '' ? this.selectPage : 'pages/Index';
// 保存 window 对象 后续 通过该对象 拉起指定的页面
if (this.currentWindowStage === null) {
// 第一次启动 保存
this.currentWindowStage = windowStage
}
windowStage.loadContent(this.selectPage || 'pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
// 清空 传递进来的 页面名称,避免重复打开即可
this.selectPage = ''
// 2in1设备,不需要全屏
if (deviceInfo.deviceType != '2in1') {
// 开启全屏
const win = windowStage.getMainWindowSync()
win.setWindowLayoutFullScreen(true)
// 获取安全区域
const top = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)
.topRect
AppStorage.setOrCreate<number>('safeTop', px2vp(top.height))
const bottom = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR)
.bottomRect
AppStorage.setOrCreate<number>('safeBottom', px2vp(bottom.height))
}
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
products\phone\src\main\ets\widget\pages\WidgetCard.ets
@Entry
@Component
struct WidgetCard {
/*
* The title.
*/
readonly TITLE: string = '我是一个卡片';
/*
* The action type.
*/
readonly ACTION_TYPE: string = 'router';
/*
* The ability name.
*/
readonly ABILITY_NAME: string = 'EntryAbility';
/*
* The message.
*/
readonly MESSAGE: string = 'add detail';
/*
* The width percentage setting.
*/
readonly FULL_WIDTH_PERCENT: string = '100%';
/*
* The height percentage setting.
*/
readonly FULL_HEIGHT_PERCENT: string = '100%';
fileNameList: string[] = new Array(2).fill('')
build() {
Column() {
Row() {
Text('今日推荐')
.fontColor('#fff')
.fontSize(16)
}
.height(40)
.width('100%')
.justifyContent(FlexAlign.Center)
Row() {
ForEach(
this.fileNameList,
(url: string) => {
Row() {
Image('')
.borderRadius(12)
.width(50)
.aspectRatio(1)
}
.backgroundColor('#eee')
.borderRadius(12)
}
)
}
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
.layoutWeight(1)
.padding({
left: 20,
right: 20
})
.backgroundColor('#fff')
.borderRadius({
topRight: 16,
topLeft: 16
})
.onClick(() => {
// postCardAction 卡片可以使用 和应用通信的一个 api
postCardAction(this, {
action: 'router', // 通信的方式 是 router
abilityName: 'EntryAbility', // ability 的名字,当前应用写这个即可
params: { targetPage: 'pages/RecommendPage' } // 传递的参数
});
})
}
.linearGradient({
angle: '135',
colors: [
['#FD3F8F', 0],
['#FF773C', 1]
]
})
.height('100%')
}
}