3. Harmony 高级UI组件

Posted by DH on November 4, 2024

Tabs

Tabs一般作为应用的骨架,能够让应用通过视图的切换,快速达到不同的功能。

包含两个部分:

  1. TabBar:TabBar是导航区域,也就是上图的下方4个按钮,可以根据用于的需要设置添加的数量,位置可以设置为上下左右

  2. TabContent:承载每一个TabBar的内容显示。切换TabBar就会切换到对应的显示内容;

用法如下:

首先定义一个数据集,用于初始化TabBar,每一个TabBar包含图片和名字,因此:

  1. TabBar的bean:
export class IconBean{
  iconNormalImage:Resource;
  iconPressedImage:Resource;
  iconName:string;

  constructor(iconNormalImage: Resource, iconPressedImage: Resource, iconName: string) {
    this.iconNormalImage = iconNormalImage;
    this.iconPressedImage = iconPressedImage;
    this.iconName = iconName;
  }
}
  1. TabBar数据集:
export function getIconBeanData(): IconBean[] {
  return [
    new IconBean($r('app.media.ic_icon'), $r('app.media.ic_ok'), "消息"),
    new IconBean($r('app.media.ic_icon'), $r('app.media.ic_ok'), "发现"),
    new IconBean($r('app.media.ic_icon'), $r('app.media.ic_ok'), "主页"),
    new IconBean($r('app.media.ic_icon'), $r('app.media.ic_ok'), "动态"),
    new IconBean($r('app.media.ic_icon'), $r('app.media.ic_ok'), "我的"),
  ];
}
  1. 初始化Tabs:
@Component
@Preview
@Entry
struct mainPage{
  @State tabsData:IconBean[] = getIconBeanData();

  build() {
      Tabs({barPosition:BarPosition.End}){
        ForEach(this.tabsData,(item:IconBean,index:number)=>{
          TabContent(){
              Text(item.iconName)
          }.tabBar(item.iconName)
        })
      }

        .width('100%')
  }
}

因为有了数据集,直接使用ForEach循环初始化。要设置Tabs的位置,可以通过barPosition设置。每一个TabBar的内容通过TabContent设置。

最终效果:

TabBar的UI支持自定义:


@Builder tabBuilder(title:string,imageUrlNormal:Resource){
    Column(){
      Image(imageUrlNormal).
      width(30)
        .aspectRatio(1)
      Text(title)
    }
}

整体效果:

List

基本用法

List是一个列表展示。比如通讯录、设置等等。并且支持分组展示,内容超出屏幕时,能够滚动,支持下拉刷新:

基本用法:

数据集依然使用tab的数据集,也是用Foreach进行初始化:

@Component
@Entry
@Preview
struct ListDemoPage{
  @State tabsData:IconBean[] = getIconBeanData();
  build() {
    List(){
      ForEach(this.tabsData,(item:IconBean,index:number)=>{
          ListItem(){
            Text(item.iconName);
          }
      })
    }
  }
}

同时,可以自定义每个Item的样式,通过divider函数为List设置分割线:

@Component
@Entry
@Preview
struct ListDemoPage{
  @State tabsData:IconBean[] = getListBeanData();
  build() {
    Column() {
      Row() {
        Text("鸿蒙高级UI组件")
      }

      List() {
        ForEach(this.tabsData, (item: IconBean, index: number) => {
          ListItem() {
            Row() {
              Image($r('app.media.ic_screenshot_thickness'))
                .width(30)
                .aspectRatio(1)
              Text(item.iconName);
            }
          }.padding(10) // 设置每个Item的padding
        })
      }
      .divider({
        strokeWidth: 1,
        startMargin: 30,
        endMargin: 10,
        color: Color.Grey
      }) // 设置List的分割线
    }
  }
}

List常用的用法是从网络请求到数据,然后渲染列表:

import axios, { Axios, AxiosResponse } from '@ohos/axios';
import { SongBean } from '../model/MusicData';

@Component
@Entry
struct AxiosDemoPage{
  url:string = "数据源";

   @State songList:SongBean[] = [];
  async getSongs(){
    const response:AxiosResponse = await axios.get<string,AxiosResponse<SongBean>,null>(this.url);
    this.songList = response.data;
    console.log("david",JSON.stringify(response))
  }

  build() {

    Column() {
      Button("请求网络数据")
        .onClick(() => {
          this.getSongs()
        })

      List() {
        ForEach(this.songList, (item: SongBean, index: number) => {
          ListItem() {
            Row() {
              Image(item.img).width(30)
                .aspectRatio(1)
              Text(item.name)
            }
          }.padding(10)
        })
      }.divider({strokeWidth:1,startMargin:30,endMargin:5,color:Color.Gray})
    }.padding(10)
  }
}

代码中getSongs函数异步请求一个接口,当数据返回时,网络库对数据完成解析,并对被状态变量修饰的数组songList赋值,状态改变,从而List进行渲染:

上拉刷新实现

在线上app中,List一个非常常见的功能就是上拉刷线。因为现在后端接口都会做分页,减少服务器端的压力,在上拉或下拉的时候,刷新或者加载更多,对List进行页面刷新。鸿蒙上实现有3种方式

原生实现

思路:

  1. 监听手指触摸事件,记录初始触摸位置;

  2. 监听手势滑动事件,如果滑动距离 > 0 ,则表示向下移动;

  3. 设置一个阈值,如果移动的距离大于阈值,则进行网络请求,并展示下拉刷新图标。

    这种思路是鸿蒙官网的例子使用的思路,较为复杂。鸿蒙还专门提供了一种刷新组件:refresh

Refresh实现

Refresh 组件使用起来就很方便。

  1. 列表的外层使用Refresh进行包裹;

  2. 定义刷新变量,用于控制刷新图标的展示和隐藏;

  3. 监听事件,进行相应的数据处理逻辑。

需要注意其中的两个方法:

  1. onStateChange:该方法在列表状态发生变化时回调,例如下拉、正在刷新等,可以找到对于的时机做一些处理;

  2. onRefreshing:下拉刷新的回调,需要做两件事情,第一是网络请求,拿到数据后进行相应逻辑处理,第二是在网络请求完毕后,无论网络请求成功还是失败,都修改Refresh的状态,结束刷新;

例子中,点击按钮,获取数据,并刷新列表,下来列表时,在列表状态改变时,清空列表。在刷新时进行网络请求,请求结束后刷新列表,改变刷新状态:

import axios, { Axios, AxiosResponse } from '@ohos/axios';
import { SongBean } from '../model/MusicData';

@Component
@Entry
struct AxiosDemoPage{
  url:string = "数据源";

  @State songList:SongBean[] = [];


  /**
   * 控制是否刷新,初始化时,需要为false,通过引用传递至Refresh组件内部,会自动改变值
   */
  @State isRefresh:boolean = false;

  async getSongs(){
    const response:AxiosResponse = await axios.get<string,AxiosResponse<SongBean>,null>(this.url);
    this.songList = response.data;
    console.log("david",JSON.stringify(response))
  }

  build() {


    Column() {
      Button("请求网络数据")
        .onClick(() => {
          this.getSongs()
        })
      Refresh({refreshing:$$this.isRefresh}) {
        List() {
          ForEach(this.songList, (item: SongBean, index: number) => {
            ListItem() {
              Row() {
                Image(item.img).width(30)
                  .aspectRatio(1)
                Text(item.name)
              }
            }.padding(10)
            .swipeAction({
              end: this.listItemEnd(index)
            })
          })
        }
        .divider({
          strokeWidth: 1,
          startMargin: 30,
          endMargin: 5,
          color: Color.Gray
        }).padding(10)
      }.onStateChange((status : RefreshStatus)=>{
        console.info("divid statues = ",status)
        if (status == RefreshStatus.Drag) {
            this.songList = [];
          console.info("divid statues Drag= ",status)
        }
      }).onRefreshing(async ()=>{
        console.info("divid statues 开始请求")
        await this.getSongs();
        this.isRefresh = false
      })
    }
  }

  @Builder listItemEnd(index:number){
    Row(){
      Button("删除")
        .onClick(()=>{
          this.songList.splice(index,1);
        })
    }
  }
}

第三方控件

在鸿蒙的开源库中有一个封装好的上下刷新、下拉加载的组件,可以参考:

https://ohpm.openharmony.cn/#/cn/result?sortedType=likes&page=1&q=refresh