当前位置: 首页 > article >正文

【HarmonyOS】鸿蒙系统在租房项目中的项目实战(二)

        从今天开始,博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”,对于刚接触这项技术的小伙伴在学习鸿蒙开发之前,有必要先了解一下鸿蒙,从你的角度来讲,你认为什么是鸿蒙呢?它出现的意义又是什么?鸿蒙仅仅是一个手机操作系统吗?它的出现能够和Android和IOS三分天下吗?它未来的潜力能否制霸整个手机市场呢?

今天实现一个简单的小案例,从零开始讲解如何通过鸿蒙开发实现一个租房平台的案例。

目录

房源推荐搭建

封装滚动组件

状态功能适配

想看模块搭建


房源推荐搭建

接下来我们开始编写房源推荐的相关内容,首先我们先把接口写好,这里我们先根据后端返回的接口类型的数据将接口类型编写一遍:

// 公共类型
interface BaseResponse {
  code: number;
  message: string;
}
interface BaseID { id: number }
interface BaseName { name: string }
interface BaseTitle { title: string }
interface BaseSubTitle { sub_title: string }
interface BaseImageURL { imageURL: string }

// 房源推荐数据
interface BaseRecommendData extends BaseResponse {
  data: RecommendData[]
}
interface RecommendData extends BaseID {
  housePicture: string
  tags: BaseName[]
  houseTitle: string
  address: string
  rentPriceUnit: string
  rentPriceListing: string
  rentArea: string
}

export {
  BaseRecommendData,
  RecommendData
}

然后我们根据后端的路径开始编写接口函数,将上面的类型赋值的接口的返回值当中:

import http from "../../utils/http"
import type { HomeData, BaseRecommendData } from './type'

// 统一管理接口
enum API {
  HOME_INFO = '/home/info',
  ROOM_RECOMMEND = '/house/nearbyHouses'
}

// 获取首页数据
export const reqHomeData = () =>
  http.get<any, HomeData>(API.HOME_INFO)

// 获取房源推荐数据
export const reqRecommendData = () =>
  http.get<any, BaseRecommendData>(API.ROOM_RECOMMEND)

因为房源组件的数据仅仅是该组件要使用而已,所以我们的接口数据就只需要在该组件进行调用即可,代码如下所示:

@State roomRecommentList: RecommendData[] = []
// 获取房源数据
getRoomRecommendData = async () => {
  const res: BaseRecommendData = await reqRecommendData()
  this.roomRecommentList = res.data
}

aboutToAppear(): void {
  this.getRoomRecommendData()
}

后面就是借助Grid布局调整调整样式即可,最终呈现的效果如下所示:

封装滚动组件

接下来我们开始封装滚动组件,因为搜索栏的组件可能多个页面都会用到,并且会随着页面的滚动的时候出现在顶部并渐变显示,这里我们也是需要对其颜色样式进行一个动态的渲染,这里我们都将其放置在公共组件当中去,这里也是用到了插槽的内容,具体如下:

如下代码我们封装了一个插槽,一个是用于放置滚动的主体内容,另一个是放置搜索栏:

interface IColor {
  bgColor: string
  fontColor: string
}

@Component
export default struct ScrollContainer {
  @Builder customBuilder() {}
  @BuilderParam navBuilderParam: ($$: IColor) => void = this.customBuilder
  @BuilderParam contentBuilderParam: () => void = this.customBuilder
  @State scrollY: number = 0 // 存储滚动条位置(y轴滚动距离)
  @State bgColor: string = 'rgba(0, 0, 0, 0)' // 背景颜色
  @State fontColor: string = 'rgba(255, 255, 255, 1)' // 字体颜色

  // 处理滚动事件
  handleScroll = (xOffset: number, yOffset: number) => {
    this.scrollY += yOffset // 存储滚动条位置(y轴滚动距离)
    this.calcColor() // 监听文字颜色变化范围
  }
  // 监听文字颜色变化范围
  calcColor = () => {
    if (this.scrollY < 10) {
      // 到达顶部,渐变开始
      this.bgColor = 'rgba(255, 255, 255, 0)'
      this.fontColor = 'rgba(255, 255, 255, 1)'
    } else if (this.scrollY <= 100) {
      // 渐变中(透明度 0 -> 1)
      const  colorOpacity = (this.scrollY - 10) / (100 - 10)
      this.bgColor = `rgba(255, 255, 255, ${colorOpacity})`
      this.fontColor = `rgba(0, 0, 0, ${colorOpacity})`
    } else {
      // 渐变结束
      this.bgColor = 'rgba(255, 255, 255, 1)'
      this.fontColor = 'rgba(0, 0, 0, 1)'
    }
  }

  build() {
    Stack() {
      Scroll() {
        Column() {
          this.contentBuilderParam()
        }.width('100%')
      }
      .width('100%')
      .height('100%')
      .scrollBar(BarState.Off)
      .align(Alignment.TopStart)
      .onDidScroll(this.handleScroll)
      // 搜索栏组件, 按引用传递
      this.navBuilderParam({ bgColor: this.bgColor, fontColor: this.fontColor })
    }
    .width('100%').alignContent(Alignment.TopStart)
  }
}

接下来我们需要对首页的内容进行改造一下,将原本的代码直接删掉,采用插槽的方式进行书写,因为使用@Builder会导致this指向的问题,所以我们在调用的时候,包一层箭头函数即可:

import { reqHomeData } from '../api/home'
import type { HomeData, bannerList, navList, tileList, planList } from '../api/home/type'
import { PADDING, SHADOW_RADIUS } from '../contants/size'
import SwiperLayout from '../components/Home/SwiperLayout'
import SearchBar from '../components/Home/SearchBar'
import NavList from '../components/Home/NavList'
import TitleList from  '../components/Home/TitleList'
import PlanList from '../components/Home/PlanList'
import RoomRecommend from '../components/Home/RoomRecommend'
import ScrollContainer from '../components/Container/ScrollContainer'

interface IColor {
  bgColor: string
  fontColor: string
}

@Component
export default struct Home {
  @State bannerList: bannerList[] = []
  @State navList: navList[] = []
  @State titleList: tileList[] = []
  @State planList: planList[] = []
  @State adPicture: string = ''

  // 获取首页数据
  getHomeData = async () => {
    const res: HomeData = await reqHomeData()
    this.bannerList = res.data.bannerList
    this.navList = res.data.navList
    this.titleList = res.data.tileList
    this.planList = res.data.planList
    this.adPicture = res.data.adPicture
  }

  // 初始化页面调用
  aboutToAppear(): void {
    this.getHomeData()
  }
  @Builder
  navBuilder($$: IColor) {
    // 搜索栏组件
    SearchBar({ bgColor: $$.bgColor, fontColor: $$.fontColor })
  }
  @Builder
  contentBuilder() {
    SwiperLayout({ bannerList: this.bannerList }) // 轮播图组件使用props通信
    Column() {
      NavList({ navList: this.navList }) // 导航栏组件使用props通信
      TitleList({ titleList: this.titleList }) // 标题栏组件使用props通信
      PlanList({ planList: this.planList }) // 列表组件
      Image(this.adPicture) // 广告图
        .width('100%')
        .height(60)
        .objectFit(ImageFit.Fill)
        .margin({ top: 10 })
        .shadow({ offsetX: 0, offsetY: 0, radius: SHADOW_RADIUS, color: 'rgba(0, 0, 0, 0.14)' })
    }.width('100%').padding({ left: PADDING, right: PADDING })
    RoomRecommend() // 推荐房源组件
  }
  build() {
    ScrollContainer({
      navBuilderParam: this.navBuilder,
      contentBuilderParam: () => {
        // 使用Builder函数,需要使用箭头函数指向this实例
        this.contentBuilder()
      },
    })
  }
}

最终呈现的效果如下所示,依然实现了原本的效果:

状态功能适配

        因为我们上面都是按照UI设计稿来设置的宽高度,也就是说我们把样式给写死了,当用户用其他不同的比例的显示器去查看我们设计的页面的时候,就会出现适配问题,为此我们需要对一些功能的状态适配问题进行相应的处理。

我们来到进入应用窗口的函数当中进行编写,将获取到的相关状态高度颜色等进行持久化存储:

然后我们可以在滚动容器当中拿到对应的对象:

windowStyle?: window.Window

getWindowStyle = async  () => {
  this.windowStyle = await window.getLastWindow(getContext(this))
}

在设定颜色的内容处根据滚动距离设置不同的颜色内容:

然后我们可以根据适配器的内容设置一下适配函数,在所有的定义宽高距离的内容当中调用一下该函数进行适配:

/*
 * 计算元素真正的大小:
   元素在设计稿的大小 / 设计稿总宽度 = x / 真机宽度(保证元素在不同设备占比相同)
   x = 元素在设计稿的大小 / 设计稿总宽度 * 真机宽度
 * */

const DRAFT_WIDTH = 360
// 预览器获取不到宽度,给预览器默认值360
const windowWidth = AppStorage.get('windowWidth') as number || 360
const rvp = (val: number) => {
  return val / DRAFT_WIDTH * windowWidth
}

export default rvp

然后我们给设置的每一个距离的内容都调用该函数即可:

想看模块搭建

接下来我们开始搭建想看的模块内容,首先我们先处理一下导航栏的样式内容,如下所示:

import { PADDING } from "../../contants/size"
import rvp from "../../utils/responsive"

@Component
export default struct NavBar {
  @StorageProp('topHeight') topHeight: number = 0
  build() {
    Row() {
      Row({ space: rvp(6) }) {
        Image($r('app.media.bag')).width(rvp(16)).height(rvp(16))
        Text('请写通勤地址').fontSize(rvp(12)).fontColor($r('app.color.black'))
      }
      Row({ space: rvp(28) }) {
        Column({ space: rvp(1) }) {
          Image($r('app.media.message')).width(rvp(20)).height(rvp(20)).fillColor($r('app.color.black'))
          Text('消息').fontSize(rvp(10)).fontColor($r('app.color.black'))
        }
        Column({ space: rvp(1) }) {
          Image($r('app.media.journey')).width(rvp(20)).height(rvp(20))
          Text('行程').fontSize(rvp(10)).fontColor($r('app.color.black'))
        }
      }
    }
    .width('100%')
    .height(rvp(44))
    .justifyContent(FlexAlign.SpaceBetween)
    .padding({ left: rvp(PADDING), right: rvp(PADDING) })
    .margin({ top: this.topHeight })
  }
}

接着处理一下找房的一些图片文字资源的显示:

import { PADDING } from "../../contants/size"
import rvp from "../../utils/responsive"

@Component
export default struct NavBar {
  @StorageProp('topHeight') topHeight: number = 0
  build() {
    Column() {
      Image($r('app.media.find_room')).width(rvp(60)).height(rvp(23)).margin({ left: rvp(4) })
      Text('发现你想看的房子').margin({ left: rvp(4), top: rvp(20) }).fontSize(rvp(12)).fontColor($r('app.color.black'))
      Image($r('app.media.blank')).width(rvp(234)).height(rvp(221)).alignSelf(ItemAlign.Center).margin({ top: rvp(7) })
      Text("暂无想看房源,试试如下找房方式")
        .width('100%')
        .textAlign(TextAlign.Center)
        .margin({ top: rvp(10) })
        .fontSize(rvp(16))
        .fontColor($r('app.color.black'))
        .fontWeight(600)
      Row({ space: rvp(10) }) {
        Row({ space: rvp(5) }) {
          Image($r('app.media.bus')).width(rvp(40)).height(rvp(40))
          Column({ space: rvp(3) }) {
            Text('通勤找房').fontSize(rvp(16)).fontColor($r('app.color.black'))
            Text('找公交车站附件房源').fontSize(rvp(12)).fontColor($r('app.color.gray'))
          }.alignItems(HorizontalAlign.Start)
        }
        Row({ space: rvp(5) }) {
          Image($r('app.media.map')).width(rvp(40)).height(rvp(40))
          Column({ space: rvp(3) }) {
            Text('地图找房').fontSize(rvp(16)).fontColor($r('app.color.black'))
            Text('找地图附件房源').fontSize(rvp(12)).fontColor($r('app.color.gray'))
          }.alignItems(HorizontalAlign.Start)
        }
      }
      .width('100%')
      .height(rvp(70))
      .border({ width: 1, color: $r('app.color.shadow') })
      .margin({ top: rvp(10) })
      .justifyContent(FlexAlign.SpaceAround)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
    .padding({ top: rvp(54), left: rvp(PADDING), right: rvp(PADDING) })
  }
}

然后我们将两个封装的组件在See的想看模块中进行调用,如下所示:

import NavBar from '../components/See/NavBar'
import FindRoom from '../components/See/FindRoom'

@Component
export default struct See {
  build() {
    Column() {
      NavBar()
      FindRoom()
    }
    .width('100%')
    .height('100%')
    .linearGradient({
      direction: GradientDirection.Bottom,
      colors: [['#DEFBE5', 0], ['#FFFFFF', 0.3]]
    })
  }
}

最终呈现的效果如下所示:


http://www.kler.cn/a/398403.html

相关文章:

  • 项目配置文件选择(Json,xml,Yaml, INI)
  • LLaMA-Factory全流程训练模型
  • centos7 升级openssl 与升级openssh 安装卸载 telnet-server
  • 31.3 XOR压缩和相关的prometheus源码解读
  • 处理namespace问题:Namespace not specified for AGP 8.0.0
  • L11.【LeetCode笔记】有效的括号
  • 悬浮窗,ViewPager2内嵌套RecyclerView,RecyclerView高度异常的问题分析
  • 学习threejs,使用第一视角控制器FirstPersonControls控制相机
  • GOOGLE EARTH ENGINE——利用GEE计算和下载雪的覆盖频率(SCF)和雪的消失日期(SDD)含全球除格陵兰岛外的矢量
  • 探索C++三大特性--C++ 继承详解:从概念到高级用法
  • 每日OJ题_牛客_NC114旋转字符串_C++_Java
  • STM32 | 空气净化器
  • 构建安全可靠的人工智能数据中心的关键因素
  • mac怎么看当前终端是zsh还是bash
  • 通过全球最前沿的技术解决视频拼接中时延带来的的应用缺陷,使得全景视频拼接能够真正得以大范围使用和推广的智慧地产开源了。
  • web前端开发--盒子属性
  • C++20中的概念(Concepts)到底是什么概念?
  • Android - Pixel 6a 手机OS 由 Android 15 降级到 Android 14 操作记录
  • 六:从五种架构风格推导出HTTP的REST架构
  • 2024.5 AAAiGLaM:通过邻域分区和生成子图编码对领域知识图谱对齐的大型语言模型进行微调
  • 深度学习神经网络创新点方向(具体)
  • Linux——环境基础开发工具使用1
  • React Native 全栈开发实战班 - 原生功能集成之地理位置服务
  • 常用的Anaconda Prompt命令行指令
  • 【第三课】Rust变量与数据类型(二)
  • java.sql.SQLException Parameter index out of range