电子说
本篇Codelab基于栅格布局、设备管理和多端协同,实现一次开发,多端部署的分布式新闻客户端页面。主要包含以下功能:
最终效果图如下:
本篇Codelab使用了设备管理及跨设备实现多端协同能力,需要手动替换full-SDK,并在配置文件module.json5文件requestPermissions属性中添加如下权限:
qr23.cn/AKFP8k
]完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:
本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在gitee中提供。
├──entry/src/main/ets // 代码区
│ ├──common
│ │ ├──constants
│ │ │ └──CommonConstants.ets // 常量类
│ │ └──utils
│ │ └──Logger.ets // 日志工具类
│ ├──entryability
│ │ └──EntryAbility.ets // 程序入口类
│ ├──model
│ │ └──RemoteDeviceModel.ets // 设备管理类
│ ├──pages
│ │ ├──Index.ets // 新闻列表页
│ │ └──NewsDetail.ets // 新闻详情页
│ ├──view
│ │ ├──DetailFooter.ets // 详情页页脚
│ │ ├──DetailHeadContent.ets // 新闻详情
│ │ ├──DeviceListDialog.ets // 设备列表弹窗
│ │ ├──NewsList.ets // 新闻列表
│ │ └──NewsTab.ets // 新闻页签
│ └──viewmodel
│ └──NewsDataModel.ets // 新闻数据处理
└──entry/src/main/resources // 资源文件目录
新闻列表页由页签区域和新闻列表区域组成,页签区域为自定义布局TabBuilder,新闻列表区域为Tabs组件嵌套List组件,并适配不同尺寸设备对应的栅格。新闻列表页能够左右滑动或点击页签切换新闻Tab,并设置点击新闻跳转至新闻详情页。
// NewsTab.ets
@Component
export default struct NewsTab {
@State currentIndex: number = 0;
@State currentBreakpoint: string = CommonConstants.BREAKPOINT_SM;
private newsItems: NewsData[] = [];
// 自定义页签栏
@Builder TabBuilder(title: Resource, index: number) {
Row() {
Text(title)
.fontSize(this.currentIndex === index ? $r('app.float.lager_font_size') : $r('app.float.middle_font_size'))
.fontWeight(this.currentIndex === index ? CommonConstants.FONT_WEIGHT_500 : FontWeight.Normal)
.fontColor(this.currentIndex === index ? $r('app.color.tab_font_select') : $r('app.color.font_color_gray'))
}
.layoutWeight(1)
.margin({
right: $r('app.float.news_tab_margin_right'),
left: (this.currentBreakpoint === CommonConstants.BREAKPOINT_SM && index === 0) ?
$r('app.float.news_tab_margin_left') : 0
})
.height(this.currentIndex === index ? $r('app.float.news_tab_current_height') : $r('app.float.news_tab_height'))
}
build() {
...
Tabs() {
ForEach(CommonConstants.ALL_TITLE, (title: string, index: number) = > {
TabContent() {
// 新闻内容列表
NewsList({ newsItems: NewsDataModel.getNewsByType(this.newsItems, title) })
}
.tabBar(this.TabBuilder(NewsDataModel.getTypeByStr(title), index))
}, (title: string, index: number) = > index + JSON.stringify(title))
}
.barHeight($r('app.float.news_tab_bar_height'))
.barWidth(CommonConstants.FULL_COMPONENT)
.barMode(this.currentBreakpoint === CommonConstants.BREAKPOINT_SM ? BarMode.Scrollable : BarMode.Fixed)
.onChange((index: number) = > {
this.currentIndex = index;
})
...
}
}
// NewsList.ets
@Component
export default struct NewsList {
private newsItems: NewsData[] = [];
build() {
List() {
ForEach(this.newsItems, (item: NewsData, index: number) = > {
ListItem() {
// 栅格布局
GridRow({
columns: {
sm: CommonConstants.FOUR_COLUMN,
md: CommonConstants.EIGHT_COLUMN,
lg: CommonConstants.TWELVE_COLUMN
},
breakpoints: {
value: [
CommonConstants.SMALL_DEVICE_TYPE,
CommonConstants.MIDDLE_DEVICE_TYPE,
CommonConstants.LARGE_DEVICE_TYPE
]
},
gutter: { x: $r('app.float.grid_row_gutter') }
}) {
GridCol({
span: {
sm: CommonConstants.FOUR_COLUMN,
md: CommonConstants.EIGHT_COLUMN,
lg: CommonConstants.EIGHT_COLUMN
},
offset: {
sm: CommonConstants.ZERO_COLUMN,
md: CommonConstants.ZERO_COLUMN,
lg: CommonConstants.TWO_COLUMN
}
}) {
NewsItem({ newsItem: item, isLast: index === this.newsItems.length - 1 })
}
}
}
}, (item: NewsData, index: number) = > index + JSON.stringify(item))
}
.height(CommonConstants.FULL_COMPONENT)
}
}
新闻详情页由新闻内容区域和页脚区域组成,其中新闻内容区域为Scroll组件嵌套栅格组件展示新闻详情,页脚区域为栅格布局,包含TextInput组件和三个按钮图标。
// DetailHeadContent.ets
build() {
Column() {
...
// 可滚动的容器组件
Scroll() {
// 栅格布局
GridRow({
columns: {
sm: CommonConstants.FOUR_COLUMN,
md: CommonConstants.EIGHT_COLUMN,
lg: CommonConstants.TWELVE_COLUMN
},
breakpoints: {
value: [
CommonConstants.SMALL_DEVICE_TYPE,
CommonConstants.MIDDLE_DEVICE_TYPE,
CommonConstants.LARGE_DEVICE_TYPE
]
},
gutter: { x: $r('app.float.grid_row_gutter') }
}) {
GridCol({
span: {
sm: CommonConstants.FOUR_COLUMN,
md: CommonConstants.EIGHT_COLUMN,
lg: CommonConstants.EIGHT_COLUMN
},
offset: {
sm: CommonConstants.ZERO_COLUMN,
md: CommonConstants.ZERO_COLUMN,
lg: CommonConstants.TWO_COLUMN
}
}) {
...
}
...
}
}
.padding({
bottom: $r('app.float.news_detail_padding_bottom')
})
.scrollBar(BarState.Off)
}
.margin({
left: $r('app.float.news_detail_margin'),
right: $r('app.float.news_detail_margin')
})
.height(CommonConstants.FULL_COMPONENT)
.alignItems(HorizontalAlign.Start)
}
// DetailFooter.ets
build() {
Column() {
// 分割线
Divider()
.color($r('app.color.detail_divider_color'))
.width(CommonConstants.FULL_COMPONENT)
// 栅格布局
GridRow({
columns: {
sm: CommonConstants.FOUR_COLUMN,
md: CommonConstants.EIGHT_COLUMN,
lg: CommonConstants.TWELVE_COLUMN
},
breakpoints: {
value: [
CommonConstants.SMALL_DEVICE_TYPE,
CommonConstants.MIDDLE_DEVICE_TYPE,
CommonConstants.LARGE_DEVICE_TYPE
]
},
gutter: { x: $r('app.float.grid_row_gutter') }
}) {
GridCol({
span: {
sm: CommonConstants.FOUR_COLUMN,
md: CommonConstants.EIGHT_COLUMN,
lg: CommonConstants.EIGHT_COLUMN
},
offset: {
sm: CommonConstants.ZERO_COLUMN,
md: CommonConstants.ZERO_COLUMN,
lg: CommonConstants.TWO_COLUMN
}
}) {
...
}
.margin({
left: this.currentBreakpoint === CommonConstants.BREAKPOINT_SM ? $r('app.float.footer_margin_sm') :
$r('app.float.footer_margin_other'),
right: this.currentBreakpoint === CommonConstants.BREAKPOINT_SM ? $r('app.float.footer_margin_sm') :
$r('app.float.footer_margin_other')
})
}
.backgroundColor($r('app.color.bg_color_gray'))
.height($r('app.float.footer_height'))
.width(CommonConstants.FULL_COMPONENT)
.onBreakpointChange((breakpoints) = > {
...
})
}
}
页脚点击分享按钮,弹出自定义弹窗DeviceListDialog,用于多端协同拉起应用。DeviceListDialog由两个标题栏和两个List组件构成,其中List组件使用ForEach循环渲染设备数据。
// DeviceListDialog.ets
build() {
Column() {
Row() {
...
}
.height($r('app.float.choose_device_row_height'))
.width(CommonConstants.FULL_COMPONENT)
.padding({
left: $r('app.float.dialog_padding'),
right: $r('app.float.dialog_padding')
})
// 信任设备列表
List() {
ForEach(this.trustedDeviceList, (item: deviceManager.DeviceInfo, index: number) = > {
ListItem() {
...
}
}, (item: deviceManager.DeviceInfo) = > JSON.stringify(item.deviceId))
}
Row() {
...
}
.height($r('app.float.choose_device_row_height'))
.width(CommonConstants.FULL_COMPONENT)
.padding({
left: $r('app.float.dialog_padding'),
right: $r('app.float.dialog_padding')
})
// 发现设备列表
List() {
ForEach(this.discoverDeviceList, (item: deviceManager.DeviceInfo, index: number) = > {
ListItem() {
...
}
}, (item: deviceManager.DeviceInfo) = > JSON.stringify(item.deviceId))
}
Row() {
...
}
.height($r('app.float.dialog_button_row_height'))
.padding({
top: $r('app.float.dialog_button_padding_top'),
bottom: $r('app.float.dialog_button_padding_bottom'),
left: $r('app.float.dialog_padding'),
right: $r('app.float.dialog_padding')
})
.width(CommonConstants.FULL_COMPONENT)
}
.borderRadius($r('app.float.dialog_border_radius'))
.backgroundColor($r('app.color.device_dialog_background'))
.width(CommonConstants.FULL_COMPONENT)
}
应用创建时创建一个设备管理器实例,注册设备状态监听和获取信任的设备列表。其中deviceManager类需使用full-SDK。
// EntryAbility.ets
onCreate(want: Want) {
...
// 创建设备管理器
RemoteDeviceModel.createDeviceManager(this.context);
}
// RemoteDeviceModel.ets
async createDeviceManager(context: common.UIAbilityContext): Promise< void > {
if (this.deviceManager !== undefined) {
return;
}
await new Promise((resolve: (value: Object | PromiseLike< Object >) = > void, reject:
((reason?: RejectError) = > void)) = > {
deviceManager.createDeviceManager(context.abilityInfo.bundleName, (err, value) = > {
if (err) {
reject(err);
logger.error('createDeviceManager failed.');
return;
}
this.deviceManager = value;
// 注册设备状态监听
this.registerDeviceStateListener();
// 获取信任设备列表
this.getTrustedDeviceList();
resolve(value);
})
})
}
用户点击新闻详情页底部的分享按钮,调用startDeviceDiscovery()方法,发现周边处在同一无线网络下的设备并添加设备至已发现的设备列表。
// RemoteDeviceModel.ets
startDeviceDiscovery(): void {
if (this.deviceManager === undefined) {
logger.error('deviceManager has not initialized');
this.showToast($r('app.string.no_device_manager'));
return;
}
this.deviceManager.on('deviceFound', (data) = > {
if (data === null) {
return;
}
// 监听设备发现
this.deviceFound(data);
})
this.deviceManager.on('discoverFail', (data) = > {
logger.error(`discoverFail data = ${JSON.stringify(data)}`);
})
this.deviceManager.on('serviceDie', () = > {
logger.error('serviceDie');
})
let info: deviceManager.SubscribeInfo = {
subscribeId: SUBSCRIBE_ID,
mode: CommonConstants.INFO_MODE,
medium: 0,
freq: CommonConstants.INFO_FREQ,
isSameAccount: false,
isWakeRemote: true,
capability: 0
};
// 添加设备至发现列表
this.discoverList = [];
AppStorage.setOrCreate(CommonConstants.DISCOVER_DEVICE_LIST, this.discoverList);
try {
this.deviceManager.startDeviceDiscovery(info);
} catch (err) {
logger.error(`startDeviceDiscovery failed error = ${JSON.stringify(err)}`);
}
}
在已发现的设备列表中选择设备,调用authenticateDevice()方法进行可信认证,输入PIN码,连接设备,将设备改为信任状态,添加至已信任设备列表。
// RemoteDeviceModel.ets
authenticateDevice(device: deviceManager.DeviceInfo, context: common.UIAbilityContext): void {
if (this.deviceManager === undefined) {
logger.error('deviceManager has not initialized');
this.showToast($r('app.string.no_device_manager'));
return;
}
for (let i: number = 0; i < this.discoverList.length; i++) {
if (this.discoverList[i].deviceId !== device.deviceId) {
continue;
}
let extraInfo: AuthExtraInfoInterface = {
targetPkgName: context.abilityInfo.bundleName,
appName: context.applicationInfo.name,
appDescription: context.applicationInfo.description,
business: CommonConstants.ZERO
};
let authParam: deviceManager.AuthParam = {
'authType': CommonConstants.ONE,
'extraInfo': extraInfo
};
try {
// 可信认证
this.deviceManager.authenticateDevice(device, authParam, (err) = > {
if (err) {
logger.error(`authenticateDevice error. Code is ${err.code}, message is ${err.message}`);
return;
}
})
} catch (err) {
logger.error(`authenticateDevice failed error = ${JSON.stringify(err)}`);
}
}
}
可信认证后,用户再次点击分享按钮,选择已信任设备列表中的设备,调用startAbilityContinuation()方法进行拉起应用,在另一设备中触发aboutToAppear()方法渲染当前的新闻详情页,实现跨设备启动UIAbility。
// DeviceListDialog.ets
function startAbilityContinuation(deviceId: string, newsId: string, context: common.UIAbilityContext): void {
let want: Want = {
deviceId: deviceId,
bundleName: context.abilityInfo.bundleName,
abilityName: CommonConstants.ABILITY_NAME,
parameters: {
newsId: newsId
}
};
// 拉起应用
context.startAbility(want).catch((err: Error) = > {
Logger.error(`startAbilityContinuation failed error = ${JSON.stringify(err)}`);
prompt.showToast({
message: $r('app.string.start_ability_continuation_error')
});
})
}
// NewsDetail.ets
aboutToAppear() {
let newsId: string | undefined = AppStorage.get< string >('wantNewsId');
if (newsId === undefined) {
this.newsData = (router.getParams() as Record< string, NewsData >)['newsItem'];
return;
}
// 读取跨设备传递的参数信息
this.newsData = this.newsItems.filter((item: NewsData) = > (item.newsId === newsId))[0];
}
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !