ArkTS开发系列之UI布局用法的学习认识(2.2.2下)

上篇回顾 ArkTS开发系列之UI布局用法的学习认识(2.2.1上

本篇内容 继续学习媒体查询(mediaquery)列表(List)风格(Grid/GridItem)和轮播(Swiper)

一、知识储备

1. 媒体查询(mediaquery)

① 媒体查询作为响应式设计的核心,在移动设备上应用十分广泛。可以根据不同设备类型或同设备不同状态修改应用的样式。常用于根据主题、横竖屏切换、分屏等场景展示相应布局界面、

② 用法

  • 导入媒体查询模块
import mediaQuery from '@ohos.mediaquery'; //导入媒体查询模块
  • 通过matchMediaSync接口设置媒体查询条件,保存返回的条件监听句柄Listener.例如监听横屏事件:
let listener = mediaQuery.matchMediaSync('(orientation: landscape)');//获取监听横屏事件的句柄
  • 给条件监听句柄listener绑定回调函数onPortrait,当listener检测设备状态变化时执行回调函数。在回调函数内,根据不同设备状态更改页面布局或实现业务逻辑。
  onPortrait(mediaQueryResult) { //当满足媒体查询条件时,触发
    if (mediaQueryResult.matches) { //根据横竖屏状态回调,更改相应页面布局
      this.text = '横屏'
      this.color = '#aa8888'
    } else {
      this.text = '竖屏'
      this.color = '#00c250'
    }
  }

  aboutToAppear() {
    portraitFunc = this.onPortrait.bind(this) //绑定当前应用实例
    this.listener.on('change', portraitFunc); //绑定回调函数
  }

2. 创建列表(List)

① 列表就是一个可以横向或竖向滑动的容器

② List容器里,可以包含ListItem或ListItemGroup,其中ListItemGroup用于列表数据的分组展示,其子组件也是ListItem。

3. 创建网格(Grid/GridItem)

① 风格布局,就是由行和列分割的单元格所组成,

② 特点,具体页面尺寸均分能力、子组件占比排版。

③ 适用场景:日历、计算器、九宫格图片展示等

④ Grid容器里,包含GridItem。有三种布局情况

  • 行、列数量与占比同时设置:展示固定行列数,且不可以滚动。推荐使用
  • 只设置行、列数与占比中的一个。按照设置的方向进行排布,超出组件可以滚动
  • 行列数与占比都不设置,组件在布局方向上排布,其行列数由布局方向、单个网格的宽高等多个属性共同决定。超出行列容纳范围的元素不展示,且不可滚动

4. 创建轮播(Swiper)

① 就是提供了滑动轮播显示的能力

② 自身宽高未设置时,同步子组件的宽高。如果设置了宽高,则以设置宽高值为准

二、效果一览

在这里插入图片描述

三、源码剖析

import mediaQuery from '@ohos.mediaquery'; //导入媒体查询模块
import window from '@ohos.window';

let portraitFunc = null; //获取监听横屏事件的句柄

@Entry
@Component
struct MediaQueryPage {
  @State color: string = '#00c250'
  @State text: string = '竖屏'
  listener = mediaQuery.matchMediaSync('(orientation: landscape)'); //获取监听横屏事件的句柄

  onPortrait(mediaQueryResult) { //当满足媒体查询条件时,触发
    if (mediaQueryResult.matches) { //根据横竖屏状态回调,更改相应页面布局
      this.text = '横屏'
      this.color = '#aa8888'
    } else {
      this.text = '竖屏'
      this.color = '#00c250'
    }
  }

  aboutToAppear() {
    portraitFunc = this.onPortrait.bind(this) //绑定当前应用实例
    this.listener.on('change', portraitFunc); //绑定回调函数
  }

  private changeOrientation(isLandscape: boolean) { //定义改变横竖屏状态函数
    let context = getContext(this)
    window.getLastWindow(context).then((lastWindow) => { //手动变更横竖屏状态
      lastWindow.setPreferredOrientation(isLandscape ? window.Orientation.LANDSCAPE : window.Orientation.PORTRAIT)
    })
  }

  build() {
    Column({ space: 50 }) {
      Text(this.text).fontSize(55).fontColor(this.color)

      Button() {
        Text('横屏').fontSize(55).fontColor(this.color).backgroundColor(Color.Blue)
      }.type(ButtonType.Capsule)
      .onClick(() => {
        this.changeOrientation(true)
      })

      Button() {
        Text('竖屏').fontSize(55).fontColor(this.color).backgroundColor(Color.Brown)
      }
      .onClick(() => {
        this.changeOrientation(false)
      })
    }
  }
}
const indexArr = ['热门', '古风', '沿海', '临时'] //定义右侧滚动响应位置

@Entry
@Component
struct ListPage {
  private listScroller: Scroller = new Scroller(); //初始化一个Scroller对象

  @State selectedIndex: number = 0;
  private listData: City[] = [];
  private tempData: string[] = ['日本', '韩国'];

  aboutToAppear() {
    this.listData[0] = (new City('热门城市', ['北京', '上海', '广州', '深圳']));
    this.listData[1] = (new City('古风城市', ['西安', '洛阳', '开封', '南京', '太原', '武汉', '荆州']));
    this.listData[2] = (new City('沿海城市', ['秦皇岛', '吉林', '金门', '台湾']));
  }

  @Builder itemHead(title: string) { //定义分组头组件
    Text(title)
      .fontSize(55)
      .fontColor('#1f9278')
      .width('100%')
      .backgroundColor('#f1f2f5')
      .padding(10)
  }

  @Builder itemEnd(index: number, childIndex: number) {
    Button({ type: ButtonType.Circle }) {
      Image($r('app.media.icon'))
        .width(20)
        .height(20)
    }
    .onClick(() => {
      let citys: City = this.listData[index];
      console.error(citys.cityArr.length + '')
      citys.cityArr.splice(childIndex, 1)
      console.error(citys.cityArr.length + '')
    })
  }

  build() {
    Stack({ alignContent: Alignment.BottomEnd }) {
      List({ space: 20, scroller: this.listScroller }) { //绑定Scroller对象
        ForEach(this.listData, (item, index) => { //迭代创建子组件
          ListItemGroup({ header: this.itemHead(item.title) }) { //支持分组展示
            ForEach(item.cityArr, (city: string, childIndex) => {
              ListItem() {
                Row() { //封闭单个子组装
                  Image($r('app.media.app_icon'))
                    .width(40)
                    .height(40)
                    .margin(10)
                  Text(city)
                    .fontSize(33)
                    .fontColor(0x00c250)
                }.width('100%')
              }.backgroundColor(Color.White)
              .swipeAction({ start: this.itemEnd(index, childIndex) }) //设置侧滑属性
            })
          }
          .divider({ //添加分隔线
            strokeWidth: 1,
            startMargin: 45,
            endMargin: 12,
            color: 0xf70000
          })
        })
      }
      .sticky(StickyStyle.Header) //设置吸顶
      .onScrollIndex((firstIndex: number) => {
        this.selectedIndex = firstIndex;
      })
      .scrollBar(BarState.Auto) //添加滚动条样式
      .backgroundColor(0xf7f7f7)
      .listDirection(Axis.Vertical) //默认垂直方向滑动可以不用设置,Axis.Horizontal是横向滑动
      .lanes({
        minLength: 300,
        maxLength: 500
      }) //取值为LengthConstrain类型,表示会根据LengthConstrain与List组件的尺寸自适应决定行或列数、也可以设置为number类型比如3,如果不设置默认为1
      .alignListItem(ListItemAlign.Start) //表示列表项居中对齐、默认值是Start,即首部对齐

      AlphabetIndexer({ arrayValue: indexArr, selected: 0 }) //字母表索引组件,
        .selected(this.selectedIndex)
        .height('40%')

      Button('返回顶部')
        .type(ButtonType.Capsule)
        .padding({ top: 8, left: 12, right: 12, bottom: 8 })
        .onClick(() => {
          this.listScroller.scrollToIndex(0);
        })

      Button('添加')
        .type(ButtonType.Capsule)
        .padding({ top: 8, left: 12, right: 12, bottom: 8 })
        .onClick(() => {
          TextPickerDialog.show({
            range: this.tempData,
            onAccept: (value: TextPickerResult) => {
              this.listData.push(new City("临时", [this.tempData[value.index]]))
            }
          })
        })
        .margin({ bottom: 60 })

    }
  }
}

@Observed
class City {
  public title: string;
  public cityArr: string[];

  constructor(title: string, cityArr: string[]) {
    this.title = title;
    this.cityArr = cityArr;
  }
}

@Entry
@Component
struct GridPage {
  build() {
    Row() {
      // MyComputer()
      MyCalendar()
    }
  }
}

@Component
struct MyComputer {
  @State listData: string[] = ['CE', 'C', '/', 'X', '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '=', '0', '.']

  build() {
    Grid() {
      ForEach(this.listData, key => {
        if (key === '0') {
          GridItem() {

            Text(key)
          }.backgroundColor(0xf7f7f7)
          .columnStart(1)
          .columnEnd(2)
        } else if (key === '=') {
          GridItem() {

            Text(key)
          }.backgroundColor(0xf7f7f7)
          .rowStart(4)
          .rowEnd(5)
        } else {
          GridItem() {
            Text(key)
          }.backgroundColor(0xf7f7f7)
        }
      })
    }
    .backgroundColor(Color.White)
    .rowsTemplate('1fr 1fr 1fr 1fr 1fr') //总共几行,每行占比   rowsTemplate columnTemplate只设置其中一项时,组件超过屏幕时可以滚动
    .columnsTemplate('1fr 1fr 1fr 1fr') //总共几列,每列占比
    .maxCount(4)
    .layoutDirection(GridDirection.Row) //每行最多四个
    .columnsGap(10) //列间距
    .rowsGap(10) //行间距
  }
}

@Component
struct MyCalendar {
  private scroller: Scroller = new Scroller();
  @State dayArr: string[] = ['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',
    '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',
    '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',
    '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',
    '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',
    '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',
    '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',
    '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',
    '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',
    '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',
    '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',
    '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'
  ]

  build() {
    Column({ space: 5 }) {
      Grid(this.scroller) {
        ForEach(this.dayArr, day => {
          GridItem() {
            Text(day).fontSize(33)
          }
          .backgroundColor("#f7f7f7")
        })
      }
      .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
      // .rowsTemplate('1fr 1fr 1fr 1fr 1fr')
      .columnsGap(5)
      .height('25%')
      .rowsGap(5)
      .backgroundColor(Color.White)

      Button('上一页')
        .type(ButtonType.Capsule)
        .onClick(() => {
          this.scroller.scrollPage({
            next: false
          })
        })
      Button('下一页')
        .type(ButtonType.Capsule)
        .onClick(() => {
          this.scroller.scrollPage({
            next: true
          })
        })
    }
  }
}
@Component
@Entry
struct SwiperPage {
  private swiperController: SwiperController = new SwiperController();

  build() {
    Stack() {
      Swiper(this.swiperController) {
        Text('0')
          .width('90%')
          .height('100%')
          .backgroundColor(Color.Red)
          .textAlign(TextAlign.Center)
          .fontSize(55)
        Text('1')
          .width('90%')
          .height('100%')
          .backgroundColor(Color.Blue)
          .textAlign(TextAlign.Center)
          .fontSize(55)
        Text('2')
          .width('90%')
          .height('100%')
          .backgroundColor(Color.Green)
          .textAlign(TextAlign.Center)
          .fontSize(55)
        Text('3')
          .width('90%')
          .height('100%')
          .backgroundColor(Color.Yellow)
          .textAlign(TextAlign.Center)
          .fontSize(55)
      }
      .indicatorStyle({
        size: 25,
        left: 0,
        color: Color.Orange
      })
      .displayCount(2)//可以设置一个页面内展示多个子组件
      .vertical(true)//false 水平轮播  true垂直轮播
      .indicator(true)
      .autoPlay(true)//true为自动轮播,时间间隔2秒
      .interval(2000) //true为自动轮播,时间间隔2秒
      .loop(true) //true为循环播放
      .onChange((index: number) => {
        console.error(`现在展示的是第${index}个页面`)
      })

      Row(){
        Button('上一页').onClick(() => {
          this.swiperController.showPrevious()
        })

        Button('下一页').onClick(() => {
          this.swiperController.showNext()
        })
      }
    }

  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值