文章的目的为了记录使用Arkts 进行Harmony app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 Arkts 鸿蒙应用 开发(一)工程文件分析-CSDN博客
开源 Arkts 鸿蒙应用 开发(二)封装库.har制作和应用-CSDN博客
开源 Arkts 鸿蒙应用 开发(三)Arkts的介绍-CSDN博客
开源 Arkts 鸿蒙应用 开发(四)布局和常用控件-CSDN博客
开源 Arkts 鸿蒙应用 开发(五)控件组成和复杂控件-CSDN博客
开源 Arkts 鸿蒙应用 开发(六)数据持久–文件和首选项存储-CSDN博客
开源 Arkts 鸿蒙应用 开发(七)数据持久–sqlite关系数据库-CSDN博客
开源 Arkts 鸿蒙应用 开发(八)多媒体–相册和相机-CSDN博客
开源 Arkts 鸿蒙应用 开发(九)通讯–tcp客户端-CSDN博客
开源 Arkts 鸿蒙应用 开发(十)通讯–Http-CSDN博客
开源 Arkts 鸿蒙应用 开发(十一)证书和包名修改-CSDN博客
开源 Arkts 鸿蒙应用 开发(十二)传感器的使用-CSDN博客
开源 Arkts 鸿蒙应用 开发(十三)音频–MP3播放_arkts avplayer播放音频 mp3-CSDN博客
开源 Arkts 鸿蒙应用 开发(十四)线程–任务池(taskpool)-CSDN博客
开源 Arkts 鸿蒙应用 开发(十五)自定义绘图控件–仪表盘-CSDN博客
开源 Arkts 鸿蒙应用 开发(十六)自定义绘图控件–波形图-CSDN博客
开源 Arkts 鸿蒙应用 开发(十七)通讯–http多文件下载-CSDN博客
开源 Arkts 鸿蒙应用 开发(十八)通讯–Ble低功耗蓝牙服务器-CSDN博客
推荐链接:
开源 java android app 开发(一)开发环境的搭建-CSDN博客
开源 java android app 开发(二)工程文件结构-CSDN博客
开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客
开源 java android app 开发(四)GUI界面重要组件-CSDN博客
开源 java android app 开发(五)文件和数据库存储-CSDN博客
开源 java android app 开发(六)多媒体使用-CSDN博客
开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客
开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客
开源 java android app 开发(九)后台之线程和服务-CSDN博客
开源 java android app 开发(十)广播机制-CSDN博客
开源 java android app 开发(十一)调试、发布-CSDN博客
开源 java android app 开发(十二)封库.aar-CSDN博客
推荐链接:
开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客
开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客
开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-CSDN博客
开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-CSDN博客
开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客
本章内容主要演示了一个多文件下载的HarmonyOS应用界面,主要包含三个部分:MultipleFilesDownload主组件、FileDownloadItem下载项组件和ProgressButton进度按钮组件。
1.工程结构
2.源码解析
3.演示效果
4.工程下载网址
一、工程结构,主要包括3个文件,Index.ets,FileDownloadItem.ets,ProgressButton.ets。
二、源码解析
2.1 Index.ets文件:管理多个文件的下载任务,提供"全部开始/暂停"功能,显示下载列表。
简单说明:
使用@Entry和@Component装饰器声明为入口组件,
维护一个下载URL数组downloadUrlArray
使用ForEach渲染多个FileDownloadItem组件
提供全局的开始/暂停所有下载的功能
跟踪下载计数和失败计数
以下为代码:
import { request } from '@kit.BasicServicesKit';
import { FileDownloadItem } from '../view/FileDownloadItem';
const NO_TASK: number = 0;
function downloadConfig(downloadUrl: string): request.agent.Config {
const config: request.agent.Config = {
action: request.agent.Action.DOWNLOAD,
url: downloadUrl,
overwrite: true,
method: 'GET',
saveas: './',
mode: request.agent.Mode.BACKGROUND,
gauge: true,
retry: false
};
return config;
}
@Entry
@Component
struct MultipleFilesDownload {
/**
* enter the download urls
*/
private downloadUrlArray: string[] = ["https://m.down.sandai.net/mobile/OfficialSite_MobileThunder1.apk"];
@State downloadConfigArray: request.agent.Config[] = [];
@State isStartAllDownload: boolean = false;
@State downloadCount: number = 0;
@State downloadFailCount: number = 0;
aboutToAppear(): void {
for (let i = 0; i < this.downloadUrlArray.length; i++) {
const config: request.agent.Config = downloadConfig(this.downloadUrlArray[i]);
this.downloadConfigArray.push(config);
}
this.downloadCount = this.downloadUrlArray.length;
}
build() {
Column() {
Row() {
Text($r('app.string.multiple_files_download_transfer_list'))
.fontWeight(FontWeight.Bold)
.fontSize($r('app.integer.title_font_size'))
.width('100%')
.fontColor($r('app.color.text_color'))
}
.alignItems(VerticalAlign.Bottom)
.width('91.1%')
.height(112)
Row() {
Row() {
Text($r('app.string.multiple_files_download_queue'))
.fontSize($r('app.integer.multiple_files_download_text_font_size_fourteen'))
.fontColor($r('sys.color.ohos_id_color_text_secondary'))
}.width($r('app.string.multiple_files_download_row_width'))
Row() {
Text(this.isStartAllDownload && this.downloadCount > NO_TASK ? $r('app.string.pause_all') :
$r('app.string.start_all'))
.fontSize($r('app.integer.multiple_files_download_text_font_size_fourteen'))
.fontWeight(500)
.fontColor($r('sys.color.ohos_id_color_text_primary_activated'))
.textAlign(TextAlign.End)
.width($r('app.string.multiple_files_download_row_text_width'))
.onClick(() => {
if (this.downloadCount === NO_TASK) {
this.getUIContext().showAlertDialog({
message: $r('app.string.multiple_files_download_completed'),
alignment: DialogAlignment.Center
});
return;
}
this.isStartAllDownload = !this.isStartAllDownload;
})
}.width($r('app.string.multiple_files_download_row_width'))
}
.margin({
left: 16,
right: 16,
top: $r('app.integer.multiple_files_download_margin_top_twenty_eight'),
bottom: $r('app.integer.margin_eight')
})
List() {
ForEach(this.downloadConfigArray, (item: request.agent.Config) => {
ListItem() {
FileDownloadItem({
downloadConfig: item,
isStartAllDownload: this.isStartAllDownload,
downloadCount: this.downloadCount,
downloadFailCount: this.downloadFailCount
})
}
}, (item: request.agent.Config) => JSON.stringify(item))
}
.width('100%')
.height('100%')
}
.focusable(false)
}
}
@Builder
export function getMultipleFilesDownload(): void {
MultipleFilesDownload();
}
2.2 FileDownloadItem.ets组件:显示文件名、下载进度和状态,提供暂停/继续下载功能,处理下载过程中的各种回调。
简单说明:
completedCallback: 下载完成
failedCallback: 下载失败
pauseCallback: 暂停下载
resumeCallback: 继续下载
progressCallback: 下载进度更新
responseCallback: HTTP响应
使用ProgressButton组件显示下载进度和控制按钮
处理文件大小和下载进度的显示转换
import { common } from '@kit.AbilityKit';
import { request } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { ProgressButton } from './ProgressButton';
const uiContext: UIContext | undefined = AppStorage.get('uiContext');
const TAG = 'FileDownloadItem';
const BYTE_CONVERSION: number = 1024;
const context = uiContext!.getHostContext()! as common.UIAbilityContext;
function getFileNameFromUrl(url: string): string {
const segments = url.split('/');
let tmp = segments.pop() || '';
if (tmp.indexOf('?') != -1) {
return tmp.split('?')[0];
}
return tmp;
}
@Component
export struct FileDownloadItem {
@State downloadConfig: request.agent.Config = { action: request.agent.Action.DOWNLOAD, url: '' };
@State fileName: string = '';
@State textState: string | Resource = "";
@Link @Watch('onDownLoadUpdated') isStartAllDownload: boolean;
private downloadTask: request.agent.Task | undefined = undefined;
@Link downloadCount: number;
@Link downloadFailCount: number;
@State isShow: boolean = false;
@State downloading: boolean = false;
@State sFileSize: string | number = "-";
@State nFileSize: number = 0;
@State sCurrentDownloadSize: string = "-";
@State @Watch('updateProgress') nCurrentDownloadSize: number = 0;
@State progress: number = 0;
@State isPaused: boolean = false;
private completedCallback = (progress: request.agent.Progress) => {
this.textState = $r("app.string.download_completed");
if (this.sFileSize === -1) {
this.sFileSize = this.sCurrentDownloadSize
this.nCurrentDownloadSize = 1;
}
this.downloadCount–;
}
private failedCallback = (progress: request.agent.Progress) => {
this.textState = $r("app.string.download_fail");
this.downloadFailCount++;
if (this.downloadFailCount === this.downloadCount) {
this.isStartAllDownload = false;
}
if (this.downloadTask) {
request.agent.show(this.downloadTask.tid, (err: Error, taskInfo: request.agent.TaskInfo) => {
if (err) {
hilog.error(0x0000, TAG, 'agent show error:', err);
return;
}
});
}
}
private pauseCallback = (progress: request.agent.Progress) => {
this.isPaused = true;
this.downloading = false;
this.textState = $r("app.string.paused");
}
private resumeCallback = (progress: request.agent.Progress) => {
this.isPaused = false;
this.textState = $r("app.string.downloading");
this.downloading = true;
}
private progressCallback = (progress: request.agent.Progress) => {
this.textState = $r("app.string.downloading");
this.downloading = true;
this.isShow = true;
if (this.downloadTask) {
if (this.sFileSize === '-') {
if (progress.sizes[0] === -1) {
this.sFileSize = -1;
this.nCurrentDownloadSize = 0;
this.nFileSize = 1;
} else {
this.nFileSize = progress.sizes[0];
this.sFileSize = (progress.sizes[0] / BYTE_CONVERSION).toFixed() + 'kb';
this.nCurrentDownloadSize = progress.processed;
}
} else if (this.sFileSize === -1) {
hilog.info(0x0000, TAG, 'file size unknown');
} else {
this.nCurrentDownloadSize = progress.processed;
}
this.sCurrentDownloadSize = (progress.processed / BYTE_CONVERSION).toFixed() + 'kb';
}
}
private responseCallback = (response: request.agent.HttpResponse) => {
hilog.info(0x0000, TAG, 'response:' + response.statusCode);
};
updateProgress() {
setTimeout(() => {
if (this.textState == $r("app.string.paused")) {
this.isPaused = true;
return;
}
let tmp = this.nCurrentDownloadSize / this.nFileSize * 100;
if (tmp <= this.progress) {
return;
}
this.progress = tmp;
}, 10)
}
aboutToAppear(): void {
this.fileName = getFileNameFromUrl(this.downloadConfig.url);
}
onDownLoadUpdated(): void {
if (this.isStartAllDownload) {
if (this.textState === $r("app.string.download_fail")) {
this.downloadTask = undefined;
this.isShow = false;
this.textState = "";
}
this.startDownload();
} else {
if (this.downloadFailCount > 0 && this.downloadFailCount === this.downloadCount) {
this.downloadFailCount = 0;
} else {
this.pauseDownload();
}
}
}
startDownload(): void {
if (this.downloadTask === undefined) {
request.agent.create(context, this.downloadConfig).then((task: request.agent.Task) => {
task.on('completed', this.completedCallback);
task.on('failed', this.failedCallback);
task.on('pause', this.pauseCallback);
task.on('resume', this.resumeCallback);
task.on('progress', this.progressCallback);
task.on('response', this.responseCallback);
task.start().then(() => {
this.downloadTask = task;
}).catch((err: Error) => {
hilog.error(0x0000, TAG, 'task start error:', err);
})
}).catch((err: Error) => {
hilog.error(0x0000, TAG, 'create error:', err);
});
} else {
this.resumeDownload();
}
}
pauseOrResumeDownload(): void {
if (this.downloadTask) {
request.agent.show(this.downloadTask.tid, (err: Error, taskInfo: request.agent.TaskInfo) => {
if (err) {
hilog.error(0x0000, TAG, 'agent show error:', err);
return;
}
if (taskInfo.progress.state === request.agent.State.PAUSED) {
this.resumeDownload();
} else {
this.pauseDownload();
}
});
}
}
pauseDownload(): void {
if (this.downloadTask) {
request.agent.show(this.downloadTask.tid, (err: Error, taskInfo: request.agent.TaskInfo) => {
if (err) {
hilog.error(0x0000, TAG, 'agent show error:', err);
return;
}
if (this.downloadTask && (taskInfo.progress.state === request.agent.State.WAITING || taskInfo.progress.state
=== request.agent.State.RUNNING || taskInfo.progress.state === request.agent.State.RETRYING)) {
this.downloadTask.pause().then(() => {
}).catch((err: Error) => {
hilog.error(0x0000, TAG, 'task pause error:', err);
});
}
});
}
}
resumeDownload(): void {
if (this.downloadTask) {
request.agent.show(this.downloadTask.tid, (err: Error, taskInfo: request.agent.TaskInfo) => {
if (err) {
hilog.error(0x0000, TAG, 'agent show error:', err);
return;
}
if (this.downloadTask && taskInfo.progress.state === request.agent.State.PAUSED) {
this.downloadTask.resume().then(() => {
}).catch((err: Error) => {
hilog.error(0x0000, TAG, 'task resume error:', err);
});
}
});
}
}
build() {
RelativeContainer() {
Image($r('app.media.multiple_files_download_file'))
.height($r("app.integer.multiple_files_download_image_size"))
.width($r("app.integer.multiple_files_download_image_size"))
.id('fileImage')
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center }
})
Text(this.fileName)
.fontSize($r("app.integer.multiple_files_download_text_font_size"))
.padding({ left: $r("app.integer.multiple_files_download_padding") })
.alignRules({
left: { anchor: 'fileImage', align: HorizontalAlign.End },
top: { anchor: 'fileImage', align: VerticalAlign.Top },
})
.fontWeight(FontWeight.Medium)
.margin({ top: $r('app.integer.item_name_top_margin') })
.id('fileName')
ProgressButton({
paused: this.isPaused,
progress: this.progress,
progressButtonWidth: $r('app.integer.progress_btn_width'),
content: this.textState,
enable: true,
clickCallback: () => {
this.pauseOrResumeDownload();
}
})
.visibility(this.isShow ? Visibility.Visible : Visibility.Hidden)
.alignRules({
right: { anchor: '__container__', align: HorizontalAlign.End },
center: { anchor: '__container__', align: VerticalAlign.Center }
})
Text(this.sCurrentDownloadSize + "/" + this.sFileSize)
.fontSize($r('app.integer.multiple_files_download_text_font_size_fourteen'))
.width($r('app.string.multiple_files_download_text_width'))
.fontColor($r('app.color.multiple_files_download_text_font_color'))
.margin({ top: $r("app.integer.multiple_files_download_margin_top") })
.padding({ left: $r("app.integer.multiple_files_download_padding") })
.alignRules({
top: { anchor: 'fileName', align: VerticalAlign.Bottom },
left: { anchor: 'fileImage', align: HorizontalAlign.End }
})
.id('downloadVal')
}
.height($r('app.integer.item_height'))
.margin({ left: 16, right: 16 })
.padding({ left: 12, right: 12 })
}
}
2.3 ProgressButton.ets,这个自定义的组建实现了:显示下载进度百分比,根据状态显示不同文本(下载中/暂停/完成),提供点击回调功能。
简单说明:
使用HarmonyOS的Progress组件显示进度条
根据进度值自动更新显示文本
支持启用/禁用状态
提供圆角胶囊样式
const EMPTY_STRING: string = '';
const MAX_PROGRESS: number = 100;
const MAX_PERCENTAGE: string = '100%';
const MIN_PERCENTAGE: string = '0%';
const TEXT_OPACITY: number = 0.4;
const BUTTON_NORMAL_WIDTH: number = 44;
const BUTTON_NORMAL_HEIGHT: number = 28;
const BUTTON_BORDER_RADIUS: number = 14;
const TEXT_ENABLE: number = 1.0;
const MIN_WIDTH: Length = 44;
const PADDING_TEXT: Length = 8;
const PROGRESS_BUTTON_PROGRESS_KEY = 'progress_button_progress_key';
const PROGRESS_BUTTON_PRIMARY_FONT_KEY = 'progress_button_primary_font_key';
const PROGRESS_BUTTON_CONTAINER_BACKGROUND_COLOR_KEY = 'progress_button_container_background_color_key';
const PROGRESS_BUTTON_EMPHASIZE_SECONDARY_BUTTON_KEY = 'progress_button_emphasize_secondary_button_key';
@Component
export struct ProgressButton {
@Prop @Watch('updateText') paused: boolean = false;
@Prop @Watch('getProgressContext') progress: number;
@State textProgress: string = EMPTY_STRING;
@Prop content: string | Resource = EMPTY_STRING;
@State isLoading: boolean = false;
progressButtonWidth?: Length = BUTTON_NORMAL_WIDTH;
clickCallback: () => void = () => {
};
@Prop enable: boolean = true;
@State progressColor: ResourceColor = '#330A59F7';
@State containerBorderColor: ResourceColor = '#330A59F7';
@State containerBackgroundColor: ResourceColor = $r('sys.color.ohos_id_color_foreground_contrary');
private getButtonProgress(): number {
if (this.progress < 0) {
return 0;
} else if (this.progress > MAX_PROGRESS) {
return MAX_PROGRESS;
}
return this.progress;
}
updateText() {
if (this.paused) {
setTimeout(() => {
this.isLoading = !this.paused;
}, 10);
} else {
this.isLoading = !this.paused;
}
}
private getProgressContext() {
if (this.progress < 0) {
this.isLoading = false;
this.textProgress = MIN_PERCENTAGE;
} else if (this.progress >= MAX_PROGRESS) {
this.isLoading = false;
this.textProgress = MAX_PERCENTAGE;
} else {
this.isLoading = true;
this.textProgress = Math.floor(this.progress / MAX_PROGRESS * MAX_PROGRESS).toString() + "%";
}
}
build() {
Button() {
Stack() {
Progress({
value: this.getButtonProgress(), total: MAX_PROGRESS,
style: ProgressStyle.Capsule
})
.height(BUTTON_NORMAL_HEIGHT)
.borderRadius(BUTTON_BORDER_RADIUS)
.width('100%')
.hoverEffect(HoverEffect.None)
.clip(false)
.enabled(this.enable)
.key(PROGRESS_BUTTON_PROGRESS_KEY)
.color(this.progressColor)
Text(this.isLoading ? this.textProgress : this.content)
.fontSize($r('sys.float.ohos_id_text_size_button3'))
.fontWeight(FontWeight.Medium)
.key(PROGRESS_BUTTON_PRIMARY_FONT_KEY)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.padding({ left: PADDING_TEXT, right: PADDING_TEXT })
.opacity(this.enable ? TEXT_ENABLE : TEXT_OPACITY)
Row()
.key(PROGRESS_BUTTON_CONTAINER_BACKGROUND_COLOR_KEY)
.backgroundColor(Color.Transparent)
.border({ width: 1, color: this.containerBorderColor })
.height(BUTTON_NORMAL_HEIGHT)
.borderRadius(BUTTON_BORDER_RADIUS)
.width('100%')
}
}
.borderRadius(BUTTON_BORDER_RADIUS)
.clip(false)
.hoverEffect(HoverEffect.None)
.key(PROGRESS_BUTTON_EMPHASIZE_SECONDARY_BUTTON_KEY)
.backgroundColor(this.containerBackgroundColor)
.constraintSize({ minWidth: MIN_WIDTH })
.width((!this.progressButtonWidth || this.progressButtonWidth < BUTTON_NORMAL_WIDTH) ?
BUTTON_NORMAL_WIDTH : this.progressButtonWidth)
.stateEffect(this.enable)
.onClick(() => {
if (!this.enable) {
return;
}
if (this.progress < MAX_PROGRESS) {
this.isLoading = !this.isLoading;
}
this.clickCallback && this.clickCallback();
})
}
}
2.4 module.json5权限需要添加ohos.permission.INTERNET
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone"
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "$string:reason_internet",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
},
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
]
}
}
三、演示效果,下载地址数组里只用写了1个,所以只有1个文件,需要的可以自行添加。
四、工程下载网址:https://download.csdn.net/download/ajassi2000/91685765?spm=1011.2124.3001.6210
评论前必须登录!
注册