鸿蒙实战开发:天气预报APP页面布局

本章通过一个天气应用,介绍一多应用的整体开发过程,包括UX设计、工程管理及调试、页面开发等。

UX设计

本示例中的天气应用包含主页、管理城市和添加城市三个页面,其中主页中又包含菜单和更新间隔两个弹窗,基本业务逻辑如下所示。

在这里插入图片描述

“一多”建议从最初的设计阶段开始就拉通多设备综合考虑。考虑实际智能终端设备种类繁多,设计师无法针对每种具体设备各自出一份UX设计图。“一多”建议从设备屏幕宽度的维度,将设备划分为四大类。设计师只需要针对这四大类设备做设计,而无需关心具体的设备形态。

设备类型屏幕宽度(vp)
超小设备[0, 320)
小设备[320, 600)
中设备[600, 840)
大设备[840, +∞)

说明

  • vp是virtual pixel(虚拟像素)的缩写,是常用的长度单位。

  • 此处基于设备屏幕宽度划分不同设备是为了方便理解。通常智能设备上的应用都是以全屏的形式运行,但随着移动技术的发展,当前部分智能设备支持应用以自由窗口模式运行(即用户可以通过拖拽等操作自由调整应用运行窗口的尺寸),故以应用窗口尺寸为基准进行划分更为合适,本文后续的响应式布局章节中将详细介绍相关内容。

  • HarmonyOS当前仅有默认设备和平板两种设备形态,DevEco Studio在创建HarmonyOS工程时也仅可以选择默认设备和平板。随着演进,其支持的设备形态会不断丰富,本文也会定期刷新相关介绍。

默认设备和平板对应于小设备、中设备及大设备,本示例以这三类设备场景为例,介绍不同设备上的UX设计。天气主页在不同设备上的设计图如下所示。

小设备中设备大设备
主页在这里插入图片描述在这里插入图片描述

另外,大设备中天气主页还允许用户开启或者隐藏侧边栏。

开启侧边栏隐藏侧边栏
在这里插入图片描述在这里插入图片描述

从天气应用在各设备上的UX设计图中,可以观察到如下UX的一些“规律”:

  • 在不同的屏幕宽度下,应用的整体风格基本保持一致。
  • 在相近的屏幕宽度范围内,应用的布局基本不变;在不同的屏幕宽度范围内,应用的布局有较大差异。
  • 应用在小屏幕下显示的元素,是大屏幕中显示元素的子集。
    • 考虑到屏幕尺寸及显示效果,大屏幕中可以显示的元素数量一定不少于小屏幕。
    • 为充分利用屏幕尺寸优势,大屏幕可以有其独有的元素或设计(如本示例中的侧边栏)。

如此,既在各设备上体现了UX的一致性,也在各设备上体现了UX的差异性,从而既可以保障各设备上应用界面的体验,也可以最大程度复用界面代码。

工程管理及调试

在本文DevEco Studio使用章节中,将详细介绍一多的工程创建及管理等,本小节仅介绍最基础的工程创建及多设备预览调试。

工程创建

一多应用的工程创建过程,与传统应用并无较大差异。只需在工程创建过程中,注意在“Device Type”选项中勾选所有该应用期望运行的目标设备类型,保证后续该应用可以在所有目标设备上正确安装即可。
在这里插入图片描述

预览调试

在代码开发过程中,可以开启预览器,并打开“Multi-profile preview”开关,实时观察应用在不同设备下的表现。

在这里插入图片描述

特别的,还可以点击“+ New Profile”按钮,新增自定义预览器。

在这里插入图片描述

页面开发

天气应用中涉及较多的页面和弹窗,本小节以天气主页为例,简单介绍不同设备下的页面实现思路。

观察天气主页在不同设备上的UX设计图,可以进行如下设计:

  • 将天气主页划分为9个基础区域,如:

在这里插入图片描述

  • 基础区域9仅在大设备上显示,基础区域1-8虽然在各设备上始终展示但其尺寸及区域内的布局基本保持不变,可以结合自适应布局能力以自定义组件的形式分别实现这9个基础区域。
小设备中设备大设备
主页在这里插入图片描述在这里插入图片描述
  • 基础区域1-8之间的布局在不同设备上有较大差异,可以使用响应式布局中的栅格布局能力实现组件间的布局效果。

  • 展开和隐藏侧边栏的功能可以通过侧边栏组件来实现。侧边栏是大设备上独有的,借助响应式布局中的媒体查询能力,控制仅在大设备上展示侧边栏即可。

主页基础区域

天气主页中的9个基础区域介绍及实现方案如下表所示。

编号简介实现方案
1标题栏自适应布局拉伸能力。
2天气概览Row和Column组件,并指定其子组件按照主轴起始方向对齐或居中对齐。
3每小时天气自适应布局延伸能力 。
4每日天气自适应布局延伸能力 。
5空气质量Canvas画布组件绘制空气质量图,并使用Row组件和Column组件控制内部元素的布局。
6生活指数自适应布局均分能力。
7日出日落Canvas画布组件绘制日出日落图 。
8应用信息Row和Column组件,并指定其子组件居中对齐。
9侧边导航栏综合运用自适应布局中的拉伸能力、占比能力和延伸能力 。

天气主页涉及的内容较多,因篇幅限制,本小节仅介绍区域3(每小时天气)的实现。

延伸能力是指容器组件内的子组件,按照其在列表中的先后顺序,随容器组件尺寸变化显示或隐藏。随着可用显示区域的增加,用户可以看到的“每小时天气”信息也不断增加,故“每小时天气”可以通过延伸能力实现,其核心代码如下所示。

import { Forecast, getHoursData, MyDataSource, Style } from '@ohos/common';

@Component
export default struct HoursWeather {
  private hoursData: Forecast[] = getHoursData(0);
  @State hoursDataResource: MyDataSource = new MyDataSource(this.hoursData);

  build() {
    // 通过列表组件实现延伸能力
    List() {
      LazyForEach(this.hoursDataResource, (hoursItem:IDataSource) => {
        ListItem() {
          // 具体每个小时的天气情况
          Column() { 
              // ... 
            }
        }
      })
    }
    .height(Style.CARD_HEIGHT)
    .borderRadius(Style.NORMAL_RADIUS)
    .backgroundColor(Style.CARD_BACKGROUND_COLOR)
    // 将列表方向设置为水平方向
    .listDirection(Axis.Horizontal)
  }
}

城市天气详情

天气主页右侧的城市天气详情由区域1-8组成,区域1(标题栏)始终固定在页面顶部,区域2-8在不同设备下的布局不同且可以随页面上下滚动。本小节介绍如何实现城市天气详情中区域2~8的布局效果。

设备屏幕可能无法一次性显示区域2-8的所有内容,故需要在外层增加滚动组件(即Scroll组件)以支持上下滚动。不同设备下区域2-8的相对位置一共有三套不同的布局,可以借助响应式布局中的栅格布局实现这一效果。本示例中将栅格在不同场景下分别划分为4列、8列和12列,区域2-8在不同场景下的布局如下表所示。

小设备中设备 或 大设备(侧边栏显示状态)大设备(侧边栏隐藏状态)
在这里插入图片描述在这里插入图片描述在这里插入图片描述

说明 为提升用户体验,大设备侧边栏隐藏状态下,每日天气与空气质量的相对顺序发生了改变。可以通过调整GridCol栅格子组件的order属性,实现目标效果。

import AirQuality from './AirQuality'; //组件请参考相关实例
import HoursWeather from './HoursWeather';
import IndexHeader from './IndexHeader';
import IndexEnd from './IndexEnd';
import LifeIndex from './LifeIndex';
import MultidayWeather from './MultidayWeather';
import SunCanvas from './SunCanvas';
import { CityListData, Style } from '@ohos/common';

@Component
export default struct HomeContent {
  private cityListData: CityListData | undefined = undefined;
  private index: number = 1;
  @Prop showSideBar: boolean;
  @State headerOpacity: number = 1;

  build() {
    // 支持滚动
    Scroll() {
      GridRow({
        columns: { sm: 4, md: 8, lg: this.showSideBar ? 8 : 12 },
        gutter: { x: Style.GRID_GUTTER, y: Style.GRID_GUTTER },
        breakpoints: { reference: BreakpointsReference.WindowSize } }) {
        // 天气概览
        GridCol({ span: { sm: 4, md: 8, lg: this.showSideBar ? 8 : 12 }, order: 1 }) {
          IndexHeader({ headerDate: this.cityListData.header, index: this.index })
            .opacity(this.headerOpacity)
        }
        // 每小时天气
        GridCol({ span: { sm: 4, md: 8, lg: 8 }, order: 2 }) {
          HoursWeather({ hoursData: this.cityListData.hoursData })
        }
        // 每日天气
        GridCol({ span: 4, order: {sm: 3, md: 3, lg: this.showSideBar ? 3 : 4} }) {
          MultidayWeather({ weekData: this.cityListData.weekData })
        }
        // 空气质量
        GridCol({ span: 4, order: {sm: 4, md: 4, lg: this.showSideBar ? 4 : 3} }) {
          AirQuality({ airData: this.cityListData.airData, airIndexData: this.cityListData.airIndex })
        }
        // 生活指数
        GridCol({ span: 4, order: 5 }) {
          LifeIndex({ lifeData: this.cityListData.suitDate })
        }
        // 日出日落
        GridCol({ span: 4, order: 6 }) {
          SunCanvas()
        }
        // 应用信息
        GridCol({ span: { sm: 4, md: 8, lg: this.showSideBar ? 8 : 12 }, order: 7 }) {
          IndexEnd()
        }
      }
    }
    .width('100%')
  }
}

主页整体实现

综合考虑各设备下的效果,天气主页的根节点使用侧边栏组件:

  • 小设备和中设备既不展示侧边栏,也不提供控制侧边栏显示和隐藏的按钮。

  • 大设备默认展示侧边栏,同时提供控制侧边栏显示和隐藏的按钮。

另外主页右侧的城市天气详情,支持左右滑动切换城市,可以使用Swiper组件实现目标效果。

  • 小设备和中设备开启Swiper组件的导航点,引导用户通过左右滑动切换不同城市。

  • 大设备中用户通过点击侧边栏中的城市列表即可高效的切换不同城市,此时需要关闭Swiper组件的导航点。

import HomeContent from './home/HomeContent'; //组件请参考相关实例
import IndexTitleBar from './home/IndexTitleBar';
import SideContent from './home/SideContent';
import { CityListData,  getCityListWeatherData } from '@ohos/common';

@Entry
@Component
struct Home {
  @State cityListWeatherData: CityListData[] = getCityListWeatherData();
  @State curBp: string = 'md';
  @State showSideBar: boolean = false;
  
  build() {
    SideBarContainer(SideBarContainerType.Embed) {
      // 左侧侧边栏
      SideContent({ showSideBar: $showSideBar })
      // 右侧内容区
      Flex({direction: FlexDirection.Column}) {
        // 基础区域1标题栏
        IndexTitleBar({ curBp: this.curBp, showSideBar: $showSideBar })
          .height(56)
        // 天气详情,通过Swiper组件实现左右滑动切换城市的效果
        Swiper() {
          ForEach(this.cityListWeatherData, (item:CityListData, index) => {
            HomeContent({ showSideBar: this.showSideBar, cityListData: item, index: index })
          })
        }
        // 大设备关闭导航点
        .indicator(this.curBp !== 'lg')
        .width('100%')
      }
    }
    .height('100%')
    .sideBarWidth('33.3%')
    // 通过状态变量,控制不同设备下侧边栏的显隐状态
    .showSideBar(this.showSideBar)
  }
}

最终,天气首页的运行效果如下图所示。

小设备中设备大设备(隐藏侧边栏)大设备(显示侧边栏)
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

功能开发

应用开发不仅包含应用页面开发,还包括应用后端功能开发以及服务器端开发等。服务器端开发不在本文的讨论范围内,本小节仅介绍多设备上应用功能开发的注意事项。

如前文所示,本示例的目标运行设备是小设备、中设备和大设备,对应实际的设备类型为默认设备和平板等。这些设备运行的都是标准系统,其系统能力一致,所以无需做特别考虑。但是在超小设备(对应的实际设备类型为智能穿戴设备等)上,考虑CPU、内存、硬盘等硬件限制,往往会对系统进行裁剪。如果在应用后端功能开发时调用当前系统没有的能力,就可能会引发异常。

通常有两种方式解决上述问题:

  • 在应用安装包中描述其需要的系统能力,保证本应用仅被分发和安装到可以满足其诉求的系统中。

  • 在使用特定系统能力前,通过canIUse接口判断系统能力是否存在,进而执行不同的逻辑。

<think>好的,用户想了解如何在HarmonyOS天气预报App中实现管理城市页面。首先,我需要结合用户提供的三个引用内容,特别是引用3,其中提到使用城市ID存储和调用。另外,引用2提到了界面设计,使用ArkUI框架,Flex和Grid组件,以及顶部导航栏的布局。 首先,管理城市页面通常包括城市列表、添加和删除功能。根据引用3,城市数据需要存储,所以应该使用HarmonyOS的数据管理能力,比如Preferences或关系型数据库。然后,界面部分需要展示城市列表,每个城市可能有删除按钮,顶部有添加按钮跳转到添加页面。添加页面需要搜索或选择城市,调用和风天气API查询城市ID,如引用1提到的HTTP请求。 接下来,要考虑ArkUI组件的使用,比如List组件展示城市,每个列表项用Flex布局,包含城市名和删除按钮。添加按钮放在导航栏右侧,点击后跳转到添加城市页面,该页面可能有搜索框和结果列表,调用API后存储城市ID。 数据存储方面,使用Preferences保存城市ID数组,每次添加或删除时更新。在添加城市页面,输入城市名后,发送HTTP请求到和风天气的搜索API,获取城市ID,验证后保存到本地。 还需要处理用户交互,比如点击删除按钮时弹出确认对话框,确保数据安全。另外,多设备适配方面,引用2提到使用Flex和Grid组件,确保布局在不同屏幕尺寸上表现良好。 最后,测试功能是否正常,包括添加、删除城市,以及数据持久化。可能还需要考虑网络错误处理,比如API调用失败时的提示。 总结步骤:数据存储设计、界面布局、API调用、用户交互处理、多设备适配。确保各部分符合HarmonyOS开发规范,如使用ArkUI组件和HTTP库。</think>### HarmonyOS天气预报App管理城市页面实现方式 #### 1. 数据存储设计 - **城市ID存储**:使用`Preferences`或`关系型数据库`存储城市ID数组,每个城市对应和风天气API的唯一标识[^3] ```typescript // 示例:使用Preferences存储城市列表 import preferences from '@ohos.data.preferences'; let cityListKey = "cityIdList"; let prefs: preferences.Preferences = await preferences.getPreferences(this.context, "weatherDB"); // 添加城市 async function addCity(cityId: string) { let cities = await prefs.get(cityListKey, JSON.stringify([])); let cityArray = JSON.parse(cities); if (!cityArray.includes(cityId)) { cityArray.push(cityId); await prefs.put(cityListKey, JSON.stringify(cityArray)); } } // 删除城市 async function removeCity(index: number) { let cities = await prefs.get(cityListKey, JSON.stringify([])); let cityArray = JSON.parse(cities); cityArray.splice(index, 1); await prefs.put(cityListKey, JSON.stringify(cityArray)); } ``` #### 2. 界面实现(ArkUI) - **主界面结构**:采用`Column`+`List`布局,包含顶部导航栏和可滚动城市列表 ```typescript @Entry @Component struct CityManagementPage { @State cityList: Array<string> = [] build() { Column() { // 顶部导航栏 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { Text('城市管理').fontSize(20) Button('+').onClick(() => { router.pushUrl({ url: 'pages/AddCityPage' }) }) }.padding(10) // 城市列表 List({ space: 10 }) { ForEach(this.cityList, (cityId: string, index: number) => { ListItem() { Flex({ justifyContent: FlexAlign.SpaceBetween }) { Text(this.getCityName(cityId)) // 调用城市ID转换方法 Button('删除').onClick(() => { this.handleDelete(index) }) } } }) } } } // 获取城市名称方法(需对接API) private getCityName(cityId: string) { // 实现与本地缓存/API的对接 } // 删除处理 private handleDelete(index: number) { prompt.showDialog({ message: '确认删除该城市?', buttons: [{ text: '取消' }, { text: '确定' }] }).then(result => { if (result.index === 1) { removeCity(index); this.cityList.splice(index, 1); } }) } } ``` #### 3. 添加城市页面 - **API对接**:通过和风天气的`城市搜索API`实现查询功能[^1] ```typescript // 示例:城市搜索请求 import http from '@ohos.net.http'; function searchCities(keyword: string) { let httpRequest = http.createHttp(); httpRequest.request( "https://geoapi.qweather.com/v2/city/lookup?key=API_KEY&location=" + keyword, { method: http.RequestMethod.GET, header: { 'Content-Type': 'application/json' } }, (err, data) => { if (!err) { let result = JSON.parse(data.result); // 处理返回的城市数据 } } ); } ``` #### 4. 关键实现要点 1. **数据同步**:城市列表更新后需触发`@State`变量刷新视图 2. **多设备适配**:使用`栅格布局`和`百分比尺寸`适配不同屏幕[^2] 3. **网络异常处理**:添加`try/catch`块处理API请求失败 4. **本地缓存**:对频繁访问的城市数据建立`LRU缓存`机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值