#DAYU200体验官# ArkUI eTS实践开发一个管家服务系统(二) 原创 精华
DAYU200开发板eTS 实现的若干效果合集
@toc
前言
感谢润和软件提供的DAYU200的开发板,为开发者提供了便利,为开源事业所做出的贡献!
这段时间用OpenHarmony eTS在DAYU200开发板做了一些实践,也实现了一些效果,这篇文章就来做一个汇总和展示。同时,这段时间也看到社区里每天都会有新的帖子,新的小伙伴加入,一派欣欣向荣的景象…看到交流群里大家的交流和讨论,看到其他老师和小伙伴在社区的成果,这些都激励自己,愿社区越来越繁荣,愿鸿蒙越来越好!
实现的一些效果展示
项目结构
1.我的通知
知识点:
-
使用了Column布局、Flex流式布局、时间格式化工具类等
-
Badge (消息气泡)的使用
Column() { Badge({ count: this.nCounts, //消息数量 maxCount: 99, position: BadgePosition.RightTop, //位置 style: { //样式设置 color: '#CCFFBB', fontSize: 16, badgeSize: 20, badgeColor: Color.Red } })
-
RDB数据的使用
参考以前写过的一篇:
https://ost.51cto.com/posts/12975#1modelBookDataModelts__138 -
效果展示
Badge效果 | 通知详情页 | 支付页面 |
---|---|---|
2.语音服务
知识点:
-
Flex流式布局
-
Lottie 动画的使用
//导入lottieEts组件库
import lottieEts from '@ohos/lottieETS'
@State listening: boolean = true
@State answer: string = '您好,有什么吩咐!'
private listeningWord: string = '我正在聆听..'
private doneWord: string = '好的,\n已经帮您搞定啦!'
//Lottie动画
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private animateItem: any = null
private animateName: string = "grunt";
private path: string = "common/balls.json"
//动画Canvas
Row() {
Canvas(this.context)
.width('100%')
//.height(90)
.border({ style: BorderStyle.Dashed })
.onAppear(() => {
this.animateItem = lottieEts.loadAnimation({
container: this.context,
renderer: 'canvas',
loop: 1,
autoplay: true,
name: this.animateName,
path: this.path
})
Animator('__lottie_ets')
//监听播放完成
this.animateItem.addEventListener('loopComplete', () => {
logger.getInstance(this).debug(`loopComplete...`)
this.answer = this.listeningWord;
})
})
.onDisAppear(() => {
logger.getInstance(this).debug(`destroy lottie...`)
lottieEts.destroy(this.animateName);
})
}
.align(Alignment.Center)
.alignItems(VerticalAlign.Center)
.alignSelf(ItemAlign.Center)
.height('40%')
.onClick(() => {
//播放动画
this.playing()
this.answer = '我想想看啊...';
})
//播放lottie动画
playing() {
logger.getInstance(this).debug(`player...`)
if (this.animateItem !== null) {
//从250帧开始播放
//this.animateItem.goToAndPlay(50, true);
//延迟到下轮循环播放再生效
this.animateItem.resetSegments(false)
lottieEts.play(this.animateName)
}
}
- RPC服务调用,模拟AI翻译功能
封装了ServiceModel.ts 对Ability的建立连接、断开连接、以及连接后的回调进行了一层封装,sendRequest 还是在page中进行调用。
//模拟翻译功能
translateWord() {
logger.getInstance(this).debug(`translateWord begin`)
//获取RPC代理对象
let mRemote = this.serviceModel.getRemoteObject()
if (mRemote === null) {
prompt.showToast({
message: 'please connect service'
})
}
//发送请求数据封装
let option: rpc.MessageOption = new rpc.MessageOption()
//入参
let data: rpc.MessageParcel = rpc.MessageParcel.create()
data.writeString(this.beforeSortString)
//返回数据
let reply: rpc.MessageParcel = rpc.MessageParcel.create()
//发送请求
mRemote.sendRequest(1, data, reply, option, () => {
this.afterSortString = reply.readString()
this.answer = '翻译好啦!'
})
}
- 效果展示
Lottie动画 | 连接服务 | 连接失败 |
---|---|---|
Lottie更多内容参考:https://gitee.com/openharmony/app_samples/tree/master/ETSUI/Lottie
如果要下载Lottie动画json文件,可以去去搜索LottieFiles
3.通信管理
知识点:
-
Column基础布局,使用了TextInput、Button等组件。
-
Socket 通信的使用
之前已经写过一篇,介绍了UDP、TCP的使用,去参考:https://ost.51cto.com/posts/13151 -
效果展示
4.灯光管理
知识点:
-
Column基础布局,希望实现一个可以选择颜色的拾色板和一个可以控制灯光亮度的滑块。
-
linearGradient渐变色的使用,实现拾色板
Row() { Canvas() .linearGradient({ angle: 145, colors: [ [0xFF0000, 0.0], [0xF0F000, 0.3], [0x00FF00, 0.5], [0x00F0F0, 0.6], [0x0000FF, 1.1] ] }) .borderRadius(20) //圆角设置 .height('40%') .width('90%') .onClick(() => { logger.getInstance(this).debug(`you choice color is ${this.lightcolor}`) }) }
-
实现一个可以滑块,用于控制灯光亮度
Row() { Row() { Image(this.imageMap.get('light_sun')) .width(50) .height(50) .margin({ left: 40 }) Text(this.lightpercent) .fontSize(50) .margin({ left: 20 }) .width('200') .fontWeight(FontWeight.Bold) .textAlign(TextAlign.Start) //.backgroundColor(Color.Gray) } .width(this.lightpercent) //亮度进度条百分比 .height('100%') .borderRadius(20) //圆角设置 .backgroundColor('#ffd5d9d9') .align(Alignment.Start) } .onTouch((e) => { switch (e.type) { //TouchType.Down 手指按下时触发 case TouchType.Down: //记录手指按下时的X、Y坐标 this.touchX = e.touches[0].screenX; this.touchY = e.touches[0].screenY; this.lightvolumn = this.touchX if (this.lightvolumn > 325) { this.lightvolumn = 325 } if (this.lightvolumn < 10) { this.lightvolumn = 10 } let tmp = this.lightvolumn / 325 * 100 logger.getInstance(this).debug(` tmp: ${tmp}`) this.lightpercent = tmp.toFixed(0) + '%' logger.getInstance(this).debug(` lightpercent: ${this.lightpercent}`) break //TouchType.Move 手指按压移动时触发 case TouchType.Move: //记录手指移动时的X、Y移动值 let moveX = this.touchX - e.touches[0].screenX; let moveY = this.touchY - e.touches[0].screenY; //当手指移动方向:X为正(手指往左边移动)且Y方向上下不超30时,拉出删除按钮 logger.getInstance(this).debug(`you touch moveX: ${moveX},moveY:${moveY}`) this.lightvolumn -= moveX if (this.lightvolumn > 325) { this.lightvolumn = 325 } if (this.lightvolumn < 10) { this.lightvolumn = 10 } logger.getInstance(this).debug(` lightvolumn2: ${this.lightvolumn}`) let tmp2 = this.lightvolumn / 325 * 100 logger.getInstance(this).debug(` tmp: ${tmp2}`) this.lightpercent = tmp2.toFixed(0) + '%' logger.getInstance(this).debug(` lightpercent: ${this.lightpercent}`) break //TouchType.Up 手指抬起时触发 case TouchType.Up: break } }) .width('90%') .height('25%') .margin({ top: 20 }) .borderRadius(20) //圆角设置 .backgroundColor('#cb5c5c5f') }
-
效果展示
初始值 | 滑动调节亮度 | |
---|---|---|
- 存在问题
拾色板功能一般是如何实现的,如何获取颜色,有什么好的思路,欢迎留言
5.场景服务
场景服务,预期是想实现对接入设备的自动化控制,并且可以进行定制。
知识点:
-
Column、Row基础布局
-
animateTo结合rotate实现动画
//四个场景颜色块的角度 @State angle: Array<number> = [0, 0, 0, 0] //切换效果 @State toggle: Array<boolean> = [false,false,false,false] //动画控制 animateTo({ duration: 1000 }, () => { this.toggle[0] = !this.toggle[0] if (this.toggle[0]) { this.bg = Color.Pink this.angle[0] = 45 } else { this.bg = Color.White this.angle[0] = 360 } })
-
效果展示
初始值 | 点击效果 | 点击效果 |
---|---|---|
后期可以添加场景,并结合设备的使用场景实现场景内容的配置。
6.智慧生活
知识点:
-
Stack布局、Flex、Tabs组件 实现华为标准app的Tab页切换效果,初级模拟了华为的智慧生活app效果。
查看官方示例学习到原来底部自定义的Tab要和展示区Tabs组件建立关联的原理是通过TabsController 来实现的!!//Tabs控制器 private controller: TabsController = new TabsController() build() { Stack({ alignContent: Alignment.BottomStart }) { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.End, justifyContent: FlexAlign.End }) { Tabs({ barPosition: BarPosition.End, index: 0, controller: this.controller//注意这里 }) { TabContent() { LifePage({ is_landscape: $is_landscape }) } TabContent() { this.buildLoading(new CanvasRenderingContext2D( new RenderingContextSettings(true))) } TabContent() { this.buildLoading(new CanvasRenderingContext2D( new RenderingContextSettings(true))) } TabContent() { this.buildLoading(new CanvasRenderingContext2D( new RenderingContextSettings(true))) } TabContent() { this.buildLoading(new CanvasRenderingContext2D( new RenderingContextSettings(true))) } } .onChange((index: number) => { //更改当前选中的Tab索引 this.bottomTabIndex = index }) .vertical(false) .barHeight(0) .width('100%') .scrollable(false) BottomTabs({ controller: this.controller, //注意这里 bottomTabIndex: $bottomTabIndex, is_landscape:$is_landscape }) } .width('100%') .layoutWeight(1) .backgroundColor(this.backgroundColor) } .width('100%').height('100%') }
@Builder
buildLoading(context: any) {
Column() {
Canvas(context)
.width('100%')
.height('50%')
.onAppear(() => {
try {
this.animateItem = lottieEts.loadAnimation({
container: context,
renderer: 'canvas',
loop: true,
autoplay: true,
name: this.animateName,
path: this.path
})
} catch (e) {
console.error(`error:${e}`)
}
})
.onDisAppear(() => {
lottieEts.destroy(this.animateName)
})
Text('开发中...').fontSize(30).fontColor(Color.White)
}
.width('100%')
.height('100%')
.backgroundColor(Color.Brown)
}
页面底部Tab页面实现代码(bottomTabs.ets)
let tabs = [
$rawfile('tabs/ic_public_home.png'),
$rawfile('tabs/ic_public_appstore.png'),
$rawfile('tabs/ic_public_search_things.png'),
$rawfile('tabs/ic_public_video.png'),
$rawfile('tabs/ic_user_portrait.png'),
]
let tabs_selected = [
$rawfile('tabs/ic_public_home_filled.png'),
$rawfile('tabs/ic_public_appstore_filled.png'),
$rawfile('tabs/ic_public_search_things_filled.png'),
$rawfile('tabs/ic_public_video_filled.png'),
$rawfile('tabs/ic_user_portrait_filled.png'),
]
let tabs_text = [
'家居', '商城', '发现', '视频', '我的'
]
function getTabSrc(tabIndex: number, index: number) {
let imgSrc = tabs[index]
if (tabIndex === index) {
imgSrc = tabs_selected[tabIndex]
}
return imgSrc
}
function getTabTextColor(tabIndex: number, index: number) {
let color = '#000000'
if (tabIndex === index) {
color = '#0091FF'
}
return color
}
function getTabText(index: number) {
let text = tabs_text[index]
return text
}
@Component
export struct BottomTabs {
private tabSrc: number[] = [0, 1, 2, 3, 4]
private backgroundColor: string = '#cbcdd0'
//Tab页控制器
private controller: TabsController = new TabsController()
@Link bottomTabIndex: number
@Link is_landscape:boolean
build() {
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(this.tabSrc, item => {
Column() {
Image(getTabSrc(this.bottomTabIndex, item))
.objectFit(ImageFit.Contain)
.width('60%').height('60%')
Text(getTabText(item))
.fontSize(15)
.fontWeight(FontWeight.Bolder)
.fontColor(getTabTextColor(this.bottomTabIndex, item))
}
.onClick(() => {
//改变Tab页控制器的索引
if (item != this.bottomTabIndex) {
this.controller.changeIndex(item)
}
})
}, item => item.toString())
}
.width('100%').height(this.is_landscape? 60:90)
.padding(5)
.backgroundColor(this.backgroundColor)
}
}
-
媒体查询控制横竖屏显示
//媒体查询 import mediaquery from '@ohos.mediaquery'
//初始化媒体查询监听 listener = mediaquery.matchMediaSync('screen and (1500 < width) and (orientation: landscape)') //是否横屏 @State is_landscape: boolean = false;
aboutToAppear() { //开启监听 this.listener.on("change", landscapeFunc) //对横屏进行额外处理 var landscapeFunc = this.onLandscape.bind(this) //bind current js instance } //横屏时执行的回调 onLandscape(mediaQueryResult) { if (mediaQueryResult.matches) { this.is_landscape = true } else { this.is_landscape = false } }
-
效果展示
初始值 | 切换TAB | 横屏效果 |
---|---|---|
更详细的内容可以参考:https://gitee.com/openharmony/app_samples/tree/master/AppSample/Shopping
媒体查询可以参考:
https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-methods-media-query-0000001211727455#ZH-CN_TOPIC_0000001211727455__li16426122219256
7.通知管理
- notification通知组件的使用,创建系统通知,显示在通知栏,用户可以通过SystemUI查看所有通知消息。
- 发布wantAgent通知
预期的效果:发布通知----在通知栏查看,点击通知 ----显示一个 “查看详情” 按钮,点击按钮,跳转到通知详情页。
/**
* 发布通知
* @param data
*/
publish() {
logger.getInstance(this).debug(`publish...`)
try {
//WantAgentInfo对象
var wantAgentInfo = {
wants: [
{
deviceId: "",
bundleName: 'com.example.lanls',
abilityName: 'com.example.lanls.NoticePageAbility',
action: "",
entities: [],
uri: "",
parameters: {'routeSource':10}
}
],
operationType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
}
// 获取wantAgent对象
wantAgent.getWantAgent(wantAgentInfo)
.then((data) => {
this.noticeWantAgent = data
//构造NotificationRequest对象
var notificationRequest = {
content: {
contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: "您有一条物业缴费通知",
text: "2022年的第一季度物业费,请各位业主于2月1号前缴纳,谢谢!",
additionalText: "12312312312"
},
},
actionButtons: [
{
title: '查看详情',
wantAgent: this.noticeWantAgent,
}
],
id: 1,
label: 'TEST666',
slotType: notification.SlotType.CONTENT_INFORMATION,
//deliveryTime: new Date().getTime()
}
//request: NotificationRequest, userId: number
notification.publish(notificationRequest)
.then((data) => {
logger.getInstance(this).debug('publish success data : ' + JSON.stringify(data))
}).catch((err) => {
logger.getInstance(this).error('publish failed because ' + JSON.stringify(err));
});
})
.catch((reason) => {
logger.getInstance(this).error(`publish error:${reason}`)
})
} catch (err) {
logger.getInstance(this).error(`publish err:${err}`)
}
}
-
如何从一个PageAbility 路由到另外一个PageAbility ?
例如,从MainAbility 到 NoticePageAbility,用下面的方式无法实现
router.push({ uri: '../../NoticePageAbility/pages/notice_page' })
需要通过打开一个Ability的方式
//通知Ability意图 private noticeAbilityWant = { want: { bundleName: 'com.example.lanls', abilityName: 'com.example.lanls.NoticePageAbility', //测试传递参数 parameters:{noticeId:10000} } } featureAbility.startAbility(this.noticeAbilityWant) .then((result) => { console.info('Operation successful. result: ' + result); //结束自己 featureAbility.terminateSelf() }).catch((error) => { console.error('Operation failed. Cause: ' + JSON.stringify(error)); }) })
https://gitee.com/openharmony/app_samples/tree/master/common/Notification
- 效果展示
通知管理 | 发布通知 | 通知栏效果 |
---|---|---|
- 发现问题
a.DAYU200的通知栏,有时候不停的闪动不知是什么原因?
b.如果希望打开一个PageAbility非默认的页面ets方式该如何操作?
8.RPC服务
知识点:
- Column基础布局,OperateView自定义组件及@Link语法糖的使用;ForEach的使用;prompt组件的使用。
- ets调用rpc 服务,实现字符串排序功能
导入rpc模块 和 ServiceModel模块(提供了连接ability的封装)
import rpc from "@ohos.rpc"
import { ServiceModel } from '../model/ServiceModel'
连接Ability
this.serviceModel.connectService(this.want)
发送请求
//发送请求数据封装
let option: rpc.MessageOption = new rpc.MessageOption()
//入参
let data: rpc.MessageParcel = rpc.MessageParcel.create()
data.writeString(this.beforeSortString)
//返回数据
let reply: rpc.MessageParcel = rpc.MessageParcel.create()
//await
mRemote.sendRequest(1, data, reply, option, () => {
this.afterSortString = reply.readString()
})
ServiceModel.ts 实现
import prompt from '@ohos.prompt'
import featureAbility from '@ohos.ability.featureAbility'
import rpc from "@ohos.rpc"
//代理对象
let mRemote: rpc.IRemoteObject = null
//连接状态码
let connection: number = -1
let TAG: string = '[LANLS.ServiceModel]'
export class ServiceModel {
private sendMessage: string = ''
public getRemoteObject() {
return mRemote
}
/**
* 连接服务
* @param deviceId
*/
connectService(want: any): number {
console.log(`${TAG} connectService begin`)
//回调处理,The remote object instance
let options = {
onConnect: function (elementName, proxy) {
mRemote = proxy
console.log(`${TAG} onConnect:${elementName},proxy:${proxy}`)
prompt.showToast({
message: 'connect service success',
})
},
onDisconnect: function (elementName) {
console.log(`${TAG} onDisconnect element:${elementName}`)
prompt.showToast({
message: `disconnect service success`
})
},
onFailed: function (e) {
console.log(`${TAG} onFailed errCode:${e}`)
prompt.showToast({
message: `connect onFailed:${e}`
})
}
}
//开始连接,Returns the number code of the ability connected
connection = featureAbility.connectAbility(want, options)
console.log(`${TAG} connection:${connection}`)
return connection
}
/**
* 断开连接
*/
disconnectService() {
console.log(`${TAG} onDisconnectService begin`)
mRemote = null
if (connection === -1) {
prompt.showToast({
message: 'onDisconnectService not connected yet'
})
return
}
//开始断开连接
featureAbility.disconnectAbility(connection).then(() => {
connection = -1
prompt.showToast({
message: 'onDisconnectService disconnect done'
})
}).catch((error) => {
console.error(`${TAG} onDisconnectService error:${error}`)
})
}
}
AIServiceAbility是一个 ServiceAbility,代码实现如下
import rpc from "@ohos.rpc"
const TAG: string = '[AIServiceAbilityStub]'
class AIServiceAbilityStub extends rpc.RemoteObject {
constructor(des: any) {
if (typeof des === 'string') {
super(des)
}
}
onRemoteRequest(code: number, data: rpc.MessageParcel, reply: rpc.MessageParcel) {
console.log(`${TAG} onRemoteRequest called...`)
//字符串排序服务
if (code === 1) {
let string = data.readString()
console.log(`${TAG} sort string=${string}`)
let result = Array.from(string).sort().join('')
console.log(`${TAG} sort result=${result}`)
reply.writeString(result)
}
//翻译服务
else if (code === 2) {
let string = data.readString()
console.log(`${TAG} translate string=${string}`)
//模拟翻译功能
let result = ''
if (string == '鸿蒙系统') {
result = 'HarmonyOS'
} else {
result = 'sorry'
}
console.log(`${TAG} sort result=${result}`)
reply.writeString(result)
} else {
console.log(`${TAG} unknown request code`)
}
return true;
}
}
export default {
onStart() {
console.info(`${TAG} onStart...`)
},
onStop() {
console.info(`${TAG} onStop`)
},
onConnect(want) {
console.log(`${TAG} onConnect, want:${JSON.stringify(want)}`)
return new AIServiceAbilityStub("first ts service stub")
},
onDisconnect(want) {
console.log(`${TAG} onDisconnect, want:${JSON.stringify(want)}`)
},
onCommand(want, startId) {
console.log(`${TAG} onCommand, want:${JSON.stringify(want)},startId:${startId}`)
}
}
- 效果展示
界面效果 | 调用服务 | |
---|---|---|
楼主的作品一步步完善起来了,贴一下第一篇的链接方便小伙伴跳转:https://ost.51cto.com/posts/12139
前来围观大佬秀操作
比预期的还差得远 /(ㄒoㄒ)/~~
感谢支持!
赞一下