快速入门HarmonyOS应用开发(三)

目录

前言

1、应用沉浸式

2、主页框架搭建

3、实现效果

前言

本系列案例完整代码已上传GitHub:HarmonyDemos 

------觉得还行的麻烦动动发财的小手点个小星星啦^_^

今天咱们接着上一篇中的功能来继续实现案例Demo,做完闪屏页之后肯定是要到主页了,那今天咱们就来搭建一下APP的主页框架,实现底部的Tab和对应页面的切换,话不多说,开干!

1、应用沉浸式

首先来给咱们的应用实现沉浸式效果,参考官网中的实现方式:

开发应用沉浸式效果

我们选择的方案是窗口全屏布局方案,整体上分为以下几个步骤:

①、调用setWindowLayoutFullScreen()接口设置窗口全屏

②、使用getWindowAvoidArea()接口获取当前布局遮挡区域

③、注册监听函数,动态获取避让区域的实时数据

④、布局中的UI元素需要避让状态栏和导航区域

实现代码:在EntryAbility的onWindowStageCreate()的loadContent()回调中添加代码

// 使用同步方式获取应用主窗口
      let windowClass: window.Window = windowStage.getMainWindowSync()
      // 设置状态栏字体颜色
      windowClass.setWindowSystemBarProperties({
        statusBarContentColor: '#FFFFFF'
      })
      // 设置窗口全屏
      windowClass.setWindowLayoutFullScreen(true)
      // 获取布局避让遮挡的区域
      let topType = window.AvoidAreaType.TYPE_SYSTEM
      let topArea = windowClass.getWindowAvoidArea(topType)
      let topRectHeight = topArea.topRect.height
      AppStorage.setOrCreate('topRectHeight', topRectHeight)
      let bottomType = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR
      let bottomArea = windowClass.getWindowAvoidArea(bottomType)
      let bottomRectHeight = bottomArea.bottomRect.height
      AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight)
      // 注册监听函数,动态获取避让区域的实时数据
      windowClass.on('avoidAreaChange', (data) => {
        if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {
          let topRectHeight = data.area.topRect.height
          AppStorage.setOrCreate('topRectHeight', topRectHeight)
        } else if (data.type == window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) {
          let bottomRectHeight = data.area.bottomRect.height
          AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight)
        }
      })

关于第4步需要在实际的页面中设置布局的底部Padding:

NavDestination() {
}.hideTitleBar(true)
 .padding({bottom:DisplayUtil.getBottomRectHeight(this.getUIContext())})

这里用到了一个工具方法,其实就是对之前存储的数据进行获取之后封装的一个方法,代码如下:

export class DisplayUtil {

  static getTopRectHeight(context: UIContext): number {
    return context.px2vp(AppStorage.get<number>('topRectHeight') as number)
  }

  static getBottomRectHeight(context: UIContext): number {
    return context.px2vp(AppStorage.get<number>('bottomRectHeight') as number)
  }
}

从上面的代码中可以看到咱们有使用AppStorage进行存储和获取数据,关于AppStorage的使用,大家可以参考:

AppStorage:应用全局的UI状态存储

它是HarmonyOS中提供的用于全局内存缓存的机制,与应用进程绑定,需要注意的是它并不是持久化的存储,是内存缓存。

2、主页框架搭建

想要实现底部Tab之间的切换效果,我们需要使用到一个新的系统提供的组件Tabs组件,参考:

选项卡 (Tabs)

打开MainPage页面,我们需要的整体结构是上下结构,上面是每个Tab的页面,下面是Tab按钮。

首先,我们来自定义底部的Tab按钮,这里放置5个按钮,先来定义每个按钮的样式:

@State currentTabIndex: number = CommonConstant.TAB_INDEX
@Builder
  TabItem(tabInfo: TabInfo) {
    Column() {
      Image(this.currentTabIndex === tabInfo.index ? tabInfo.selectImg : tabInfo.img)
        .objectFit(ImageFit.Contain)
        .width($r('app.float.vp_24'))
        .height($r('app.float.vp_24'))
        .margin({ bottom: $r('app.float.vp_5') })
      Text(tabInfo.title)
        .width(CommonConstant.FULL_PERCENT)
        .height($r('app.float.vp_16'))
        .fontSize($r('app.float.fp_12'))
        .textAlign(TextAlign.Center)
        .fontColor(this.currentTabIndex === tabInfo.index ? $r("app.color.color_primary") :
        $r('app.color.color_unselect'))
    }
    .width(CommonConstant.FULL_PERCENT)
    .height(CommonConstant.FULL_PERCENT)
    .justifyContent(FlexAlign.Center)
    .width(CommonConstant.PERCENT_20)
    .onClick(() => {
      this.currentTabIndex = tabInfo.index
    })
  }

这里我们用到了@Builder这个装饰器,它是自定义的构建函数,关于它的使用,大家可以参考:

@Builder装饰器:自定义构建函数

在点击这个按钮的时候,通过对上面定义的currentTabIndex变量值的改变来切换不同的下标。

接着,通过循环来实现5个Tab按钮的创建:

@Builder
  BottomTabView() {
    Column() {
      Divider().width(CommonConstant.FULL_PERCENT).color(Color.Black)
        .opacity(CommonConstant.OPACITY_01).strokeWidth(CommonConstant.WEIGHT_1)
      Row() {
        ForEach(tabInfos, (button: TabInfo) => {
          this.TabItem(button)
        })
      }
      .width(CommonConstant.FULL_PERCENT)
      .height($r('app.float.vp_50'))
      .alignItems(VerticalAlign.Center)
      .backgroundColor(Color.White)
    }.width(CommonConstant.FULL_PERCENT)
  }

这里我们使用了ForEach来进行循环渲染,关于ForEach的详细使用,大家可以参考:

ForEach:循环渲染

这里ForEach的第一个参数是我们提前准备好的Tabs的数据,我们把它放在了src/main/ets/model下面:

export class TabInfo {
  index: number = 0
  img: Resource = $r('app.media.tab_index')
  selectImg: Resource = $r('app.media.tab_indexed')
  title: Resource = $r('app.string.tab_index')
}

const tabInfos: TabInfo[] = [
  {
    index: 0,
    img: $r('app.media.tab_index'),
    selectImg: $r('app.media.tab_indexed'),
    title: $r('app.string.tab_index')
  },
  {
    index: 1,
    img: $r('app.media.tab_financial'),
    selectImg: $r('app.media.tab_financialed'),
    title: $r('app.string.tab_financial')
  },
  {
    index: 2,
    img: $r('app.media.tab_video'),
    selectImg: $r('app.media.tab_videoed'),
    title: $r('app.string.tab_video')
  },
  {
    index: 3,
    img: $r('app.media.tab_message'),
    selectImg: $r('app.media.tab_messaged'),
    title: $r('app.string.tab_msg')
  },
  {
    index: 4,
    img: $r('app.media.tab_mine'),
    selectImg: $r('app.media.tab_mined'),
    title: $r('app.string.tab_mine')
  }
]

export { tabInfos }

做完上面这些之后,我们就可以来实现整个页面的结构了,在MainPage的NavDestination中通过Column组件来完成上下结构的布局,上面使用Tabs组件,下面使用自定义的BottomTabView():

build() {
    NavDestination() {
      Column() {
        Tabs({ index: this.currentTabIndex }) {
          TabContent() {
            HomePage()
          }

          TabContent() {
            FinancialPage()
          }

          TabContent() {
            VideoPage()
          }

          TabContent() {
            MessagePage()
          }

          TabContent() {
            MinePage()
          }
        }
        .barHeight(CommonConstant.TAB_BAR_HEIGHT)
        .scrollable(false)
        .layoutWeight(CommonConstant.WEIGHT_1)
        .onChange((index) => {
          this.currentTabIndex = index
        })

        this.BottomTabView()
      }
    }.hideTitleBar(true)
    .padding({bottom:DisplayUtil.getBottomRectHeight(this.getUIContext())})
  }

可以看到我们一共创建了5个TabContent(),每个里面就是对应Tab承载的内容区域了,简单理解可以称它为页面,实际上它们都是自定义组件,比如,这里第一个TabContent()里面我们创建了HomePage()组件,依此类推我们又创建了余下的四个页面。

下面来看一下HomePage()里面都有些什么吧?由于目前我们只是为了实现页面的整体结构,所以这里咱们就简单放置了一个文本进行展示,代码如下:

@Component
export struct HomePage {
  build() {
    Column() {
      StatusBarComponent()
      Column() {
        Text($r('app.string.tab_index'))
          .fontSize($r('app.float.fp_20'))
          .fontColor($r('app.color.color_primary'))
          .fontWeight(FontWeight.Bold)
      }.layoutWeight(CommonConstant.WEIGHT_1)
      .justifyContent(FlexAlign.Center)
    }
    .width(CommonConstant.FULL_PERCENT)
    .height(CommonConstant.FULL_PERCENT)
  }
}

细心的大家会发现这不是还有一个StatusBarComponent()吗?它是我们自定义的一个状态栏横条,里面也很简单,只有一个跟顶部状态栏高度一致的View,代码如下:

@Component
export struct StatusBarComponent {
  build() {
    Blank().height(DisplayUtil.getTopRectHeight(this.getUIContext())).backgroundColor($r('app.color.color_primary'))
  }
}

通过上面的步骤我们已经完成了首页整体结构的搭建。

3、实现效果

下面,让我们一起来看一下最终的实现效果吧:

主页面结构录屏

OK,今天的内容就这么多啦,下期再会!

祝:工作顺利,生活愉快!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值