当前位置:   article > 正文

HarmonyOS官网案例解析——新闻数据加载(ArkTS)_arkts案例

arkts案例

官网效果图

本篇Codelab是基于ArkTS的声明式开发范式实现的样例,主要介绍了数据请求和touch事件的使用。包含以下功能:
1.数据请求。
2.列表下拉刷新。
3.列表上拉加载。
官方代码链接:[https://gitee.com/harmonyos/codelabs/tree/master/NewsDataArkTS](Codelabs: 分享知识与见解,一起探索HarmonyOS的独特魅力。 - Gitee.com)

一、相关概念

  • List组件:列表包含一系列相同宽度的列表项。
  • Tabs:通过页签进行内容视图切换。
  • TabContent:仅在Tabs中使用,对应一个切换页签的内容视图。
  • 数据请求:提供HTTP数据请求能力。
  • 触摸事件onTouch:触摸动作触发调用该方法。

二、相关权限

网络数据请求需要申请权限:ohos.permission.INTERNET。

三、约束与限制

需要搭建服务端环境,参照使用说明NewsDataArkTS,参照文档打开DevEco Studio中Terminal进行配置,默认预览器可以打开预览,不需要开模拟器(重点是电脑内存扛不住)。

四、Tab和TabContent的使用

一个Tab对应多个TabContent,每个TabContent会有一个页签标题,因此会有一个页签的数组。

创建页签标题数组

import { CommonConstant as Const } from '../common/constant/CommonConstant';

export class NewsViewModel{
  getDefaultTypeList(): NewsTypeBean[]{
    return Const.TabBars_DEFAULT_NEWS_TYPES
  }
}

export default new NewsViewModel()

export class NewsTypeBean{
  id: number = 0
  name:ResourceStr = ''
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

实现Tab标签功能

在index.ets中使用该数组

@Component
export default struct TabBar {
  @State tabBarArray:NewsTypeBean[] = NewsViewModel.getDefaultTypeList()
}
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
通过查看API,我们大致知道怎么使用了(模板代码,不需要记忆)
请添加图片描述

@Component
export default struct TabBar {

  @State tabBarArray:NewsTypeBean[] = NewsViewModel.getDefaultTypeList()
  @State currentIndex: number = 0;
  @State currentPage: number = 1;

  build() {
    Tabs(){
      ForEach(this.tabBarArray,(tabsItem:NewsTypeBean,index:number)=>{
        TabContent(){
          Column(){
            Text('11')
          }
        }.tabBar(this.TabBuilder(tabsItem.id))
      },(item:NewsTypeBean)=>JSON.stringify(item))
    }
    .barHeight(Const.TabBars_BAR_HEIGHT)
    .barMode(BarMode.Scrollable)
    .barWidth(Const.TabBars_BAR_WIDTH)
    .onChange((index: number) => {
      this.currentIndex = index;
      this.currentPage = 1;
    })
    .vertical(false)
  }

  @Builder TabBuilder(index:number){
    Column(){
      Text(this.tabBarArray[index].name)
        .height(Const.FULL_HEIGHT)
        .padding({ left: Const.TabBars_HORIZONTAL_PADDING, right: Const.TabBars_HORIZONTAL_PADDING })
        .fontSize(this.currentIndex === index ? Const.TabBars_SELECT_TEXT_FONT_SIZE : Const.TabBars_UN_SELECT_TEXT_FONT_SIZE)
        .fontWeight(this.currentIndex === index ? Const.TabBars_SELECT_TEXT_FONT_WEIGHT : Const.TabBars_UN_SELECT_TEXT_FONT_WEIGHT)
        .fontColor('#182431')
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

至此,Tab功能已实现。
在这里插入图片描述
新建NewsList.ets,将currentIndex传递至子组件

import NewsViewModel,{ NewsTypeBean} from '../viewModel/NewsViewModel'
import { CommonConstant as Const } from '../common/constant/CommonConstant';
import NewsList from '../view/NewsList'

@Component
export default struct TabBar {
  build() {
    ...
        TabContent(){
          Column(){
            NewsList({currentIndex:$currentIndex})
          }
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

NewsList中的代码如下

@Component
export default struct NewsList {
  @Link currentIndex:number

  build() {
    Column() {
      Text(this.currentIndex.toString())
        .fontColor(Color.Red)
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

至此,我们发现TabContent中的内容与页面索引保持一致了。
在这里插入图片描述

五、请求网络

module.json5中别忘了加上网络权限

    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  • 1
  • 2
  • 3
  • 4
  • 5

1.初步完成数据请求

(1)定义网络请求工具类
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();

  // Processes the data and returns.
  // 处理数据,并返回
  return responseResult.then((value: http.HttpResponse) => {
    if (value.responseCode === Const.HTTP_CODE_200) {
      // Obtains the returned data.
      // 处理数据,并返回
      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 = `${'网络错误'}&${value.responseCode}`;
    }
    return serverData;
  }).catch(() => {
    serverData.msg = '网络错误';
    return serverData;
  })
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
(2)在NewsViewModel.ets中定义请求的方法,并配置解析的参数
import prompt from '@ohos.prompt';
import { CommonConstant as Const } from '../common/constant/CommonConstant';
import { httpRequestGet } from '../common/utils/HttpUtil';
import Logger from '../common/utils/Logger';

export class NewsViewModel {
 ....

  // 获取服务端新闻数据列表
  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(Const.TabBars_DEFAULT_NEWS_TYPES)
        }
      })
        .catch((err: Error) => {
          Logger.error('getNewsList failed', JSON.stringify(err));
          reject(Const.TabBars_DEFAULT_NEWS_TYPES)
        });
    });
  }
}

...

export class ResponseResult {
  code: string
  msg: ResourceStr
  data: string | Object | ArrayBuffer

  constructor() {
    this.code = '';
    this.msg = '';
    this.data = '';
  }
}

export class NewsData {
  title: string = ''
  content: string = ''
  imagesUrl: Array<NewsFile> = [];
  source: string = ''
}

export class NewsFile {
  id: number = 0
  url: string = ''
  type: number = 0
  newId: number = 0
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

(3)在NewsList.ets中使用已定义好的方法

调用页面的生命周期aboutToAppear()方法,方法内再调用changeCategory()方法

  aboutToAppear() {
    this.changeCategory()
  }
  
  changeCategory() {
    NewsViewModel.getNewsList(this.currentPage, this.pageSize, Const.GET_NEWS_LIST)
      .then((data: NewsData[]) => {
        this.newsData = data;
      })
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
(4)获取数据后,渲染数据,NewsList.ets代码如下:
import NewsViewModel, { NewsData } from '../viewModel/NewsViewModel'
import { CommonConstant as Const, PageState } from '../common/constant/CommonConstant';

@Component
export default struct NewsList {
  @Link currentIndex: number
  currentPage: number = 1;
  pageSize: number = Const.PAGE_SIZE;
  @State newsData: Array<NewsData> = [];

  changeCategory() {
    NewsViewModel.getNewsList(this.currentPage, this.pageSize, Const.GET_NEWS_LIST)
      .then((data: NewsData[]) => {
        this.newsData = data;
      })
  }

  aboutToAppear() {
    this.changeCategory()
  }

  build() {
    Column() {
      List() {
        ForEach(this.newsData, (item: NewsData) => {
          ListItem() {
            Text(item.title)
              .width('100%')
              .fontColor(Color.Red)
          }
          .height(256)
          .width('100%')
          .backgroundColor(Color.White)
          .margin({ top: 12 })
          .borderRadius(Const.NewsListConstant_ITEM_BORDER_RADIUS)
        }, (item: NewsData, index?: number) => JSON.stringify(item) + index)
      }
      .width('93.3%')
      .height('100%')
      .backgroundColor('#f1f3f5')
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

sloop

案例效果分析:此时我们发现,只有首次进入的时候有“哈哈哈”的提示,此后切换页面就不会再提示,因此无法检测到页面的切换。那么,我们该怎么实现呢?
答:用==@watch==

@Component
export default struct NewsList {
  @Watch('changeCategory')@Link currentIndex: number
  ...
}
  • 1
  • 2
  • 3
  • 4
  • 5

当currentIndex改变时,会时刻调用changeCategory()方法。因此,可以做到,当页面进行切换时,可以获取当前index界面的数据。

sloop

此后在进行页面切换时,都会事实调用changeCategory()。

ps:根据页面的不同,可以拼接不同的参数,案例都是调用同一个接口,因此数据是一样的。实际工作中,可以把index拼到链接中传递给后端,让后端返回不同的参数,从而显示不同的界面。

2.网络请求优化

实际工作中,肯定有==“请求成功”、”请求失败“、”数据加载中“、”下拉刷新“、”上拉加载“==等各种场景。

参照案例,新建NewsModel.ets

export default class NewsModel {
  newsData: Array<NewsData> = [];
  currentPage: number = 1;
  pageSize: number = Const.PAGE_SIZE;
  pullDownRefreshText: Resource = $r('app.string.pull_down_refresh_text');
  pullDownRefreshImage: Resource = $r('app.media.ic_pull_down_refresh');
  pullDownRefreshHeight: number = Const.CUSTOM_LAYOUT_HEIGHT;
  isVisiblePullDown: boolean = false;
  pullUpLoadText: Resource = $r('app.string.pull_up_load_text');
  pullUpLoadImage: Resource = $r('app.media.ic_pull_up_load');
  pullUpLoadHeight: number = Const.CUSTOM_LAYOUT_HEIGHT;
  isVisiblePullUpLoad: boolean = false;
  offsetY: number = 0;
  pageState: number = PageState.Loading;
  hasMore: boolean = true;
  startIndex = 0;
  endIndex = 0;
  downY = 0;
  
  lastMoveY = 0;
  isRefreshing: boolean = false;
  isCanRefresh = false;
  isPullRefreshOperation = false;
  isLoading: boolean = false;
  isCanLoadMore: boolean = false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
import NewsViewModel, { NewsData } from '../viewModel/NewsViewModel'
import { CommonConstant as Const, PageState } from '../common/constant/CommonConstant';
import prompt from '@ohos.prompt';
import NewsModel from '../viewModel/NewsModel';

@Component
export default struct NewsList {
  @Watch('changeCategory')@Link currentIndex: number
  @State newsModel: NewsModel = new NewsModel()
	...//报错的地方自行修改,加上.newsModel即可
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
2.1 “请求成功”、”请求失败“、”数据加载中“

先定义pageState为PageState.Loading,当请求成功时,设置成PageState.Success,请求失败时,设置成PageState.Fail

先完善NewsList.ets中changeCategory代码

  • 当数据请求成功时,加一个请求成功状态码PageState.Success
  • 当数据请求失败时,加一个请求失败状态码PageState.Fail
changeCategory() {
    NewsViewModel.getNewsList(this.newsModel.currentPage, this.newsModel.pageSize, Const.GET_NEWS_LIST)
      .then((data: NewsData[]) => {
        this.newsModel.pageState = PageState.Success;
        this.newsModel.newsData = data;
      })
      .catch((err:string | Resource)=>{
        this.newsModel.pageState = PageState.Fail
      })
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

根据请求状态码,显示不同的界面,因newsModel对象中的属性被@state修饰,因此视图是可以刷新的

    Column() {
      if (this.newsModel.pageState === PageState.Success) {
        ...
      } else if (this.newsModel.pageState === PageState.Loading) {
       ...
      } else {
       ...
      }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

NewsList.ets完整代码如下:

import NewsViewModel, { NewsData } from '../viewModel/NewsViewModel'
import { CommonConstant as Const, PageState } from '../common/constant/CommonConstant';
import prompt from '@ohos.prompt';
import NewsModel from '../viewModel/NewsModel';
import promptAction from '@ohos.promptAction';
import Logger from '../common/utils/Logger';

@Component
export default struct NewsList {
  @Watch('changeCategory')@Link currentIndex: number
  @State newsModel: NewsModel = new NewsModel()

  changeCategory() {
    NewsViewModel.getNewsList(this.newsModel.currentPage, this.newsModel.pageSize, Const.GET_NEWS_LIST)
      .then((data: NewsData[]) => {
        this.newsModel.pageState = PageState.Success;
        this.newsModel.newsData = data;
      })
      .catch((err:string | Resource)=>{
        this.newsModel.pageState = PageState.Fail
      })
  }

  aboutToAppear() {
    this.changeCategory()
  }

  build() {
    Column() {
      if (this.newsModel.pageState === PageState.Success) {
        //请求成功
      } else if (this.newsModel.pageState === PageState.Loading) {
        //加载中
        Text('数据加载中')
      } else {
        //请求失败
        Text('请求失败')
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

等待视频加载时间5秒返回结果

2.2完善“请求成功”、”请求失败“、”数据加载中“具体界面
2.2.1 ”请求失败“
import NewsViewModel, { CustomRefreshLoadLayoutClass, NewsData } from '../viewModel/NewsViewModel'
import { CommonConstant as Const, PageState } from '../common/constant/CommonConstant';
import prompt from '@ohos.prompt';
import NewsModel from '../viewModel/NewsModel';
import promptAction from '@ohos.promptAction';
import Logger from '../common/utils/Logger';
import { CustomRefreshLoadLayout } from './CustomRefreshLoadLayout';

@Component
export default struct NewsList {
  @Watch('changeCategory') @Link currentIndex: number
  @State newsModel: NewsModel = new NewsModel()

  changeCategory() {
    NewsViewModel.getNewsList(this.newsModel.currentPage, this.newsModel.pageSize, Const.GET_NEWS_LIST)
      .then((data: NewsData[]) => {
        this.newsModel.pageState = PageState.Success;
        this.newsModel.newsData = data;
      })
      .catch(() => {
        this.newsModel.pageState = PageState.Fail
      })
  }

  aboutToAppear() {
    this.changeCategory()
  }

  build() {
    Column() {
      if (this.newsModel.pageState === PageState.Success) {

      } else if (this.newsModel.pageState === PageState.Loading) {

      } else {
        //请求失败
        this.FailLayout()
      }

    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.White)
    .justifyContent(FlexAlign.Center)
  }


  @Builder FailLayout() {
    Image($r('app.media.none'))
      .height(Const.NewsListConstant_NONE_IMAGE_SIZE)
      .width(Const.NewsListConstant_NONE_IMAGE_SIZE)
    Text('网络加载失败')
      .opacity(Const.NewsListConstant_NONE_TEXT_opacity)
      .fontSize(Const.NewsListConstant_NONE_TEXT_size)
      .fontColor('#182431')
      .margin({ top: Const.NewsListConstant_NONE_TEXT_margin })
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
2.2 先显示加载中,然后显示失败的界面。

先定义CustomRefreshLoadLayout.ets,画出界面,以及参数传递。

(1)在NewsViewModel.ets中添加如下代码

@Observed
export class CustomRefreshLoadLayoutClass{
  isVisible:boolean 
  imageSrc:Resource
  textValue:string
  heightValue:number

  constructor(isVisible: boolean, imageSrc: Resource, textValue: string, heightValue: number) {
    this.isVisible = isVisible;
    this.imageSrc = imageSrc;
    this.textValue = textValue;
    this.heightValue = heightValue;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

(2)在CustomRefreshLoadLayout.ets中添加如下代码

import { CustomRefreshLoadLayoutClass } from '../viewModel/NewsViewModel'
@Component
export struct CustomRefreshLoadLayout {
  @ObjectLink customRefreshLoadClass:CustomRefreshLoadLayoutClass
  build() {
    Row() {
      Image(this.customRefreshLoadClass.imageSrc)
        .width(18)
        .height(18)

      Text(this.customRefreshLoadClass.textValue)
        .margin({left:7})
        .fontSize(17)
        // .textAlign(TextAlign.Center)
    }
    .clip(true)
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .height(this.customRefreshLoadClass.heightValue)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

ps:聪明的你已经发现了,这里用到了==@Observed@ObjectLink==

总结:数组的元素是对象,当对象的属性发生修改时,不能触发视图的重新渲染。

  @Builder LoadLayout() {
    CustomRefreshLoadLayout({
      customRefreshLoadClass: new CustomRefreshLoadLayoutClass(true, $r('app.media.ic_pull_up_load'), '加载中', this.newsModel.pullDownRefreshHeight)
    })
  }
  • 1
  • 2
  • 3
  • 4
  • 5

33

我们发现,后端环境正常时,数据加载正常;后端服务没起时,数据加载异常。

完整代码如下:

import NewsViewModel, { CustomRefreshLoadLayoutClass, NewsData } from '../viewModel/NewsViewModel'
import { CommonConstant as Const, PageState } from '../common/constant/CommonConstant';
import NewsModel from '../viewModel/NewsModel';
import { CustomRefreshLoadLayout } from './CustomRefreshLoadLayout';

@Component
export default struct NewsList {
  @Watch('changeCategory') @Link currentIndex: number
  @State newsModel: NewsModel = new NewsModel()

  changeCategory() {
    NewsViewModel.getNewsList(this.newsModel.currentPage, this.newsModel.pageSize, Const.GET_NEWS_LIST)
      .then((data: NewsData[]) => {
        this.newsModel.pageState = PageState.Success;
        this.newsModel.newsData = data;
      })
      .catch(() => {
        this.newsModel.pageState = PageState.Fail
      })
  }

  aboutToAppear() {
    this.changeCategory()
  }

  build() {
    Column() {
      if (this.newsModel.pageState === PageState.Success) {
        //请求成功

      } else if (this.newsModel.pageState === PageState.Loading) {
        //加载中
        this.LoadLayout()
      } else {
        //请求失败
        this.FailLayout()
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.White)
    .justifyContent(FlexAlign.Center)
  }

  @Builder ListLayout() {
    List() {
      ForEach(this.newsModel.newsData, (item: NewsData) => {
        ListItem() {
          Text(item.title)
            .width('100%')
            .fontColor(Color.Red)
        }
        .height(256)
        .width('100%')
        .backgroundColor(Color.White)
        .margin({ top: 12 })
        .borderRadius(Const.NewsListConstant_ITEM_BORDER_RADIUS)

      }, (item: NewsData, index?: number) => JSON.stringify(item) + index)
    }
    .width('93.3%')
    .height('100%')
    .backgroundColor('#f1f3f5')
  }

  @Builder LoadLayout() {
    CustomRefreshLoadLayout({
      customRefreshLoadClass: new CustomRefreshLoadLayoutClass(true, $r('app.media.ic_pull_up_load'), '加载中', this.newsModel.pullDownRefreshHeight)
    })
  }

  @Builder FailLayout() {
    Image($r('app.media.none'))
      .height(Const.NewsListConstant_NONE_IMAGE_SIZE)
      .width(Const.NewsListConstant_NONE_IMAGE_SIZE)
    Text('网络加载失败')
      .opacity(Const.NewsListConstant_NONE_TEXT_opacity)
      .fontSize(Const.NewsListConstant_NONE_TEXT_size)
      .fontColor('#182431')
      .margin({ top: Const.NewsListConstant_NONE_TEXT_margin })
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

3.完善listItem界面

新建NewsItem.ets

import { NewsData, NewsFile } from '../viewModel/NewsViewModel'
import { CommonConstant, CommonConstant as Const } from '../common/constant/CommonConstant';

@Component
export struct NewsItem {
  newsData:NewsData = new NewsData()
  build() {
    Column() {
      Row(){
        Image($r('app.media.news'))
          .width('11.9%')
          .height(20)
          .objectFit(ImageFit.Fill)
        Text(this.newsData.title)
          .fontSize(20)
          .fontColor('#000000')
          .width('78.6%')
          .maxLines(1)
          .margin({ left: '2.4%' })
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .fontWeight(FontWeight.Regular)
      }
      .alignItems(VerticalAlign.Center)
      .height(22)

      Text(this.newsData.content)
        .fontSize(14)
        .fontColor('#000000')
        .width('93%')
        .height('16.8%')
        .maxLines(2)
        .margin({ top:'3.5%' })
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .fontWeight(FontWeight.Regular)
      
      Grid(){
        ForEach(this.newsData.imagesUrl,(itemImg:NewsFile)=>{
          GridItem(){
            Image(Const.SERVER+itemImg.url)
              .objectFit(ImageFit.Cover)
              .borderRadius(8)
          }

        },(itemImg:NewsFile,index?:number)=>JSON.stringify(itemImg)+index)
      }
      .columnsTemplate('1fr'.repeat(this.newsData.imagesUrl.length))
      .columnsGap(5)
      .rowsTemplate('1fr')
      .width('93%')
      .height('31.5%')
      .margin({left:'3.5%',right:'3.5%',top:'5%'})

      Text(this.newsData.source)
        .fontSize(Const.NewsSource_FONT_SIZE)
        .fontColor('#FF989898')
        .height(Const.NewsSource_HEIGHT)
        .width(Const.NewsSource_WIDTH)
        .maxLines(Const.NewsSource_MAX_LINES)
        .margin({ left: Const.NewsSource_MARGIN_LEFT, top: Const.NewsSource_MARGIN_TOP })
        .textOverflow({ overflow: TextOverflow.None })
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
import NewsViewModel, { CustomRefreshLoadLayoutClass, NewsData } from '../viewModel/NewsViewModel'
import { CommonConstant as Const, PageState } from '../common/constant/CommonConstant';
import NewsModel from '../viewModel/NewsModel';
import { CustomRefreshLoadLayout } from './CustomRefreshLoadLayout';
import { NewsItem } from './NewsItem';

@Component
export default struct NewsList {	
	...
  @Builder ListLayout() {
    List() {
      ForEach(this.newsModel.newsData, (item: NewsData) => {
        ListItem() {
          NewsItem({newsData:item})
        }
       ...
      }
    }
    ...
  }
	...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

image-20231220225752914

哇,看到界面,神清气爽~~~

六、下拉刷新、上拉加载

import NewsViewModel, { CustomRefreshLoadLayoutClass, NewsData } from '../viewModel/NewsViewModel'
import { CommonConstant as Const, PageState } from '../common/constant/CommonConstant';
import NewsModel from '../viewModel/NewsModel';
import { CustomRefreshLoadLayout } from './CustomRefreshLoadLayout';
import { NewsItem } from './NewsItem';
import promptAction from '@ohos.promptAction';
import Logger from '../common/utils/Logger';
import { LoadMoreLayout } from './LoadMoreLayout';
import { NoMoreLayout } from './NoMoreLayout';
import { listTouchEvent } from '../common/utils/PullDownRefresh';
import prompt from '@ohos.prompt';
import RefreshLayout from './RefreshLayout';

@Component
export default struct NewsList {
  @Watch('changeCategory') @Link currentIndex: number
  @State newsModel: NewsModel = new NewsModel()

  changeCategory() {
    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(() => {
        this.newsModel.pageState = PageState.Fail
      })
  }


  build() {
    Column() {
      if (this.newsModel.pageState === PageState.Success) {
        this.ListLayout()
      } else if (this.newsModel.pageState === PageState.Loading) {
        this.LoadLayout()
      } else {
        this.FailLayout()
      }
    }
	...
    .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)
        })
      }
	...
      ListItem(){
        if (this.newsModel.hasMore){
          LoadMoreLayout({
            loadMoreLayoutClass:new CustomRefreshLoadLayoutClass(this.newsModel.isVisiblePullUpLoad,
            this.newsModel.pullUpLoadImage,this.newsModel.pullUpLoadText,this.newsModel.pullUpLoadHeight)
          })
        }else {
          NoMoreLayout()
        }
      }
    }
    .width('93.3%')
    .height('100%')
    .backgroundColor('#f1f3f5')
    .margin({ left: Const.NewsListConstant_LIST_MARGIN_LEFT, right: Const.NewsListConstant_LIST_MARGIN_RIGHT })
    .divider({
      color: '#e2e2e2',
      strokeWidth: Const.NewsListConstant_LIST_DIVIDER_STROKE_WIDTH,
      endMargin: Const.NewsListConstant_LIST_MARGIN_RIGHT
    })
    // Remove the rebound effect.
    .edgeEffect(EdgeEffect.None)
    .offset({ x: 0, y: `${this.newsModel.offsetY}px` })
    .onScrollIndex((start: number, end: number) => {
      // Listen to the first index of the current list.
      this.newsModel.startIndex = start;
      this.newsModel.endIndex = end;
    })
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

以下文件可直接CV官网PullDownRefresh.ets、PullUpLoadMore.ets、LoadMoreLayout.ets、NoMoreLayout.ets、RefreshLayout.ets

效果如下:

33

至此,所有功能已全部完成。

代码链接:https://gitee.com/runitwolf/NewsDataArkTS

typora笔记链接:https://gitee.com/runitwolf/NewsDataArkTS/blob/master/NewsDataArkTS.md

ps: 软件有点小bug,不知道是开发软件的问题还是代码的问题,点击NewsModel的isCanLoadMore,提示No usages found in All Places Press Ctrl+Alt+F7 again to search in ‘Project Files’,但是我明明有地方使用啊,没找到解决方案。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/92125
推荐阅读
相关标签
  

闽ICP备14008679号