文章目录
- 介绍
- 相关概念
- 相关权限
- 约束与限制
- 完整示例
- 代码结构解读
- 构建主界面
- 数据请求
- 下拉刷新
- 总结
介绍
本篇Codelab是基于ArkTS的声明式开发范式实现的样例,主要介绍了数据请求和touch事件的使用。包含以下功能:
- 数据请求。
- 列表下拉刷新。
- 列表上拉加载。
相关概念
- List组件:列表包含一系列相同宽度的列表项。
- Tabs:通过页签进行内容视图切换。
- TabContent:仅在Tabs中使用,对应一个切换页签的内容视图。
- 数据请求:提供HTTP数据请求能力。
- 触摸事件onTouch:触摸动作触发调用该方法。
相关权限
网络数据请求需要申请权限:ohos.permission.INTERNET。
约束与限制
本篇Codelab需要搭建服务端环境,服务端如何搭建将在代码工程目录的README中详细介绍,文档中不再赘述。
完整示例
gitee源码地址
源码下载
新闻数据加载(ArkTS).zip
代码结构解读
本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在源码下载或gitee中提供。
├──entry/src/main/ets // ArkTS代码区
│ ├──common
│ │ ├──constant
│ │ │ └──CommonConstant.ets // 公共常量类
│ │ └──utils
│ │ ├──HttpUtil.ets // 网络请求方法
│ │ ├──Logger.ets // 日志工具类
│ │ ├──PullDownRefresh.ets // 下拉刷新方法
│ │ └──PullUpLoadMore.ets // 上拉加载更多方法
│ ├──entryability
│ │ └──EntryAbility.ts // 程序入口类
│ ├──pages
│ │ └──Index.ets // 主页面
│ ├──view
│ │ ├──CustomRefreshLoadLayout.ets // 下拉刷新、上拉加载布局文件
│ │ ├──LoadMoreLayout.ets // 上拉加载布局封装
│ │ ├──NewsItem.ets // 新闻数据
│ │ ├──NewsList.ets // 新闻列表
│ │ ├──NoMoreLayout.ets // 上拉停止布局封装
│ │ ├──RefreshLayout.ets // 下拉刷新布局封装
│ │ └──TabBar.ets // 新闻类型页签
│ └──viewmodel
│ ├──NewsModel.ets // 新闻模型类
│ └──NewsViewModel.ets // 新闻ViewModel
├──entry/src/main/resources // 资源文件目录
└──HttpServerOfNews // 服务端代码
构建主界面
本章节将介绍新闻列表页面的实现,用tabBar展示新闻分类,tabContent展示新闻列表,效果图如图所示:
在TabBar.ets文件中的aboutToAppear()方法里获取新闻分类。
// TabBar.ets
aboutToAppear() {
// 请求服务端新闻类别
NewsViewModel.getNewsTypeList().then((typeList: NewsTypeBean[]) => {
this.tabBarArray = typeList;
}).catch((typeList: NewsTypeBean[]) => {
this.tabBarArray = typeList;
});
}
在NewsList.ets文件中的aboutToAppear()方法里获取新闻数据,将数据加载到新闻列表页面ListLayout布局中。
// NewsList.ets
changeCategory() {
this.newsModel.currentPage = 1;
NewsViewModel.getNewsList(this.newsModel.currentPage, this.newsModel.pageSize, Const.GET_NEWS_LIST)
.then((data: NewsData[]) => {
this.newsModel.pageState = PageState.Success;
if (data.length === this.newsModel.pageSize) {
this.newsModel.currentPage++;
this.newsModel.hasMore = true;
} else {
this.newsModel.hasMore = false;
}
this.newsModel.newsData = data;
})
.catch((err: string | Resource) => {
promptAction.showToast({
message: err,
duration: Const.ANIMATION_DURATION
});
this.newsModel.pageState = PageState.Fail;
});
}
aboutToAppear() {
// 请求服务端新闻数据
this.changeCategory();
}
...
@Builder ListLayout() {
List() {
...
ForEach(this.newsModel.newsData, (item: NewsData) => {
ListItem() {
// 新闻数据
NewsItem({ newsData: item })
}
.height($r('app.float.news_list_height'))
.backgroundColor($r('app.color.white'))
.margin({ top: $r('app.float.news_list_margin_top') })
.borderRadius(Const.NewsListConstant_ITEM_BORDER_RADIUS)
}, (item: NewsData, index?: number) => JSON.stringify(item) + index)
...
}
...
}
数据请求
在module.json5文件中配置如右侧所示权限:
这一章节,将基于新闻数据请求来介绍如何从服务端请求数据。
// module.json5
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "$string:dependency_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
导入http模块,封装httpRequestGet方法,调用者传入url地址发起网络数据请求。
// HttpUtil.ets
import http from '@ohos.net.http';
...
export function httpRequestGet(url: string): Promise<ResponseResult> {
let httpRequest = http.createHttp();
// 发送数据请求
let responseResult = httpRequest.request(url, {
method: http.RequestMethod.GET,
readTimeout: Const.HTTP_READ_TIMEOUT,
header: {
'Content-Type': ContentType.JSON
},
connectTimeout: Const.HTTP_READ_TIMEOUT,
extraData: {}
});
let serverData: ResponseResult = new ResponseResult();
// 处理数据,并返回
return responseResult.then((value: http.HttpResponse) => {
Logger.info(`http value ${JSON.stringify(value)}`);
if (value.responseCode === Const.HTTP_CODE_200) {
// 获取返回数据
let result = `${value.result}`;
let resultJson: ResponseResult = JSON.parse(result);
if (resultJson.code === Const.SERVER_CODE_SUCCESS) {
serverData.data = resultJson.data;
}
serverData.code = resultJson.code;
serverData.msg = resultJson.msg;
} else {
serverData.msg = `${$r('app.string.http_error_message')}&${value.responseCode}`;
}
return serverData;
}).catch(() => {
serverData.msg = $r('app.string.http_error_message');
return serverData;
})
}
在NewsViewModel.ets文件中封装getNewsList方法,调用httpRequestGet方法请求服务端,用Promise异步保存返回的新闻数据列表。
// NewsViewModel.ets
// 获取服务端新闻数据列表
getNewsList(currentPage: number, pageSize: number, path: string): Promise<NewsData[]> {
return new Promise(async (resolve: Function, reject: Function) => {
let url = `${Const.SERVER}/${path}`;
url += '?currentPage=' + currentPage + '&pageSize=' + pageSize;
httpRequestGet(url).then((data: ResponseResult) => {
if (data.code === Const.SERVER_CODE_SUCCESS) {
resolve(data.data);
} else {
Logger.error('getNewsList failed', JSON.stringify(data));
reject($r('app.string.page_none_msg'));
}
}).catch((err: Error) => {
Logger.error('getNewsList failed', JSON.stringify(err));
reject($r('app.string.http_error_message'));
});
});
}
下拉刷新
本章节将以下拉刷新的功能效果来介绍touch事件的使用。效果图如图所示:
创建一个下拉刷新布局CustomLayout,动态传入刷新图片和刷新文字描述。
// CustomRefreshLoadLayout.ets
build() {
Row() {
// 下拉刷新图片
Image(this.customRefreshLoadClass.imageSrc)
...
// 下拉刷新文字
Text(this.customRefreshLoadClass.textValue)
...
}
...
}
将下拉刷新的布局添加到NewsList.ets文件中新闻列表布局ListLayout里面,监听ListLayout组件的onTouch事件实现下拉刷新。
// NewsList.ets
build() {
Column() {
if (this.newsModel.pageState === PageState.Success) {
this.ListLayout()
}
...
}
...
.onTouch((event: TouchEvent | undefined) => {
if (event) {
if (this.newsModel.pageState === PageState.Success) {
listTouchEvent(this.newsModel, event);
}
}
})
}
...
@Builder ListLayout() {
List() {
ListItem() {
RefreshLayout({
refreshLayoutClass: new CustomRefreshLoadLayoutClass(this.newsModel.isVisiblePullDown, this.newsModel.pullDownRefreshImage,
this.newsModel.pullDownRefreshText, this.newsModel.pullDownRefreshHeight)
})
...
}
}
...
}
- 在onTouch事件中,listTouchEvent方法判断触摸事件是否满足下拉条件。如右侧listTouchEvent所示:
- 在touchMovePullRefresh方法中,我们将对下拉的偏移量与下拉刷新布局的高度进行对比,如果大于布局高度并且在新闻列表的顶部,则表示达到刷新条件。如右侧touchMovePullRefresh所示:
- 在pullRefreshState方法中我们会对下拉刷新布局中的状态图片和描述进行改变,如右侧pullRefreshState所示。
- 当手指松开,才执行刷新操作。
// PullDownRefresh.ets
export function listTouchEvent(newsModel: NewsModel, event: TouchEvent) {
switch (event.type) {
...
case TouchType.Move:
if ((newsModel.isRefreshing === true) || (newsModel.isLoading === true)) {
return;
}
let isDownPull = event.touches[0].y - newsModel.lastMoveY > 0;
if (((isDownPull === true) || (newsModel.isPullRefreshOperation === true)) && (newsModel.isCanLoadMore === false))
{
// 手指移动,处理下拉刷新
touchMovePullRefresh(newsModel, event);
}
...
break;
}
}
export function touchMovePullRefresh(newsModel: NewsModel, event: TouchEvent) {
if (newsModel.startIndex === 0) {
newsModel.isPullRefreshOperation = true;
let height = vp2px(newsModel.pullDownRefreshHeight);
newsModel.offsetY = event.touches[0].y - newsModel.downY;
// 滑动偏移量大于下拉刷新布局高度,满足刷新条件。
if (newsModel.offsetY >= height) {
pullRefreshState(newsModel, RefreshState.Release);
newsModel.offsetY = height + newsModel.offsetY * Const.Y_OFF_SET_COEFFICIENT;
} else {
pullRefreshState(newsModel, RefreshState.DropDown);
}
if (newsModel.offsetY < 0) {
newsModel.offsetY = 0;
newsModel.isPullRefreshOperation = false;
}
}
}
export function pullRefreshState(newsModel: NewsModel, state: number) {
switch (state) {
...
case RefreshState.Release:
newsModel.pullDownRefreshText = $r('app.string.release_refresh_text');
newsModel.pullDownRefreshImage = $r('app.media.ic_pull_up_refresh');
newsModel.isCanRefresh = true;
newsModel.isRefreshing = false;
break;
case RefreshState.Refreshing:
newsModel.offsetY = vp2px(newsModel.pullDownRefreshHeight);
newsModel.pullDownRefreshText = $r('app.string.refreshing_text');
newsModel.pullDownRefreshImage = $r('app.media.ic_pull_up_load');
newsModel.isCanRefresh = true;
newsModel.isRefreshing = true;
break;
case RefreshState.Success:
newsModel.pullDownRefreshText = $r('app.string.refresh_success_text');
newsModel.pullDownRefreshImage = $r('app.media.ic_succeed_refresh');
newsModel.isCanRefresh = true;
newsModel.isRefreshing = true;
break;
...
default:
break;
}
}
上拉加载也是通过touch事件来实现的,此处不再赘叙,有兴趣的同学可参考代码。
总结
您已经完成了本次Codelab的学习,并了解到以下知识点:
- 使用List组件实现数据列表。
- 使用Tabs、TabContent组件实现内容视图切换。
- 网络数据请求。
- 触摸事件onTouch的使用。