鴻蒙一次開發(fā),多端部署(二)從一個(gè)例子開始

本章通過(guò)一個(gè)天氣應(yīng)用,介紹一多應(yīng)用的整體開發(fā)過(guò)程,包括UX設(shè)計(jì)、工程管理及調(diào)試、頁(yè)面開發(fā)等。

UX設(shè)計(jì)

本示例中的天氣應(yīng)用包含主頁(yè)、管理城市和添加城市三個(gè)頁(yè)面,其中主頁(yè)中又包含菜單和更新間隔兩個(gè)彈窗,基本業(yè)務(wù)邏輯如下所示。

“一多”建議從最初的設(shè)計(jì)階段開始就拉通多設(shè)備綜合考慮。考慮實(shí)際智能終端設(shè)備種類繁多,設(shè)計(jì)師無(wú)法針對(duì)每種具體設(shè)備各自出一份UX設(shè)計(jì)圖?!耙欢唷苯ㄗh從設(shè)備屏幕寬度的維度,將設(shè)備劃分為四大類。設(shè)計(jì)師只需要針對(duì)這四大類設(shè)備做設(shè)計(jì),而無(wú)需關(guān)心具體的設(shè)備形態(tài)。

設(shè)備類型 屏幕寬度(vp)
超小設(shè)備 [0, 320)
小設(shè)備 [320, 600)
中設(shè)備 [600, 840)
大設(shè)備 [840, +∞)

說(shuō)明:

  • vp是virtual pixel(虛擬像素)的縮寫,是常用的長(zhǎng)度單位,詳見視覺基礎(chǔ)小節(jié)中的介紹。
  • 此處基于設(shè)備屏幕寬度劃分不同設(shè)備是為了讀者方便理解。通常智能設(shè)備上的應(yīng)用都是以全屏的形式運(yùn)行,但隨著移動(dòng)技術(shù)的發(fā)展,當(dāng)前部分智能設(shè)備支持應(yīng)用以自由窗口模式運(yùn)行(即用戶可以通過(guò)拖拽等操作自由調(diào)整應(yīng)用運(yùn)行窗口的尺寸),故以應(yīng)用窗口尺寸為基準(zhǔn)進(jìn)行劃分更為合適,本文后續(xù)的響應(yīng)式布局章節(jié)中將詳細(xì)介紹相關(guān)內(nèi)容。
  • OpenHarmony當(dāng)前僅有默認(rèn)設(shè)備和平板兩種設(shè)備形態(tài),IDE在創(chuàng)建OpenHarmony工程時(shí)也僅可以選擇默認(rèn)設(shè)備和平板。隨著演進(jìn),其支持的設(shè)備形態(tài)會(huì)不斷豐富,本文也會(huì)定期刷新相關(guān)介紹。

默認(rèn)設(shè)備和平板對(duì)應(yīng)于小設(shè)備、中設(shè)備及大設(shè)備,本示例以這三類設(shè)備場(chǎng)景為例,介紹不同設(shè)備上的UX設(shè)計(jì)。天氣主頁(yè)在不同設(shè)備上的設(shè)計(jì)圖如下所示。

另外,大設(shè)備中天氣主頁(yè)還允許用戶開啟或者隱藏側(cè)邊欄。

從天氣應(yīng)用在各設(shè)備上的UX設(shè)計(jì)圖中,可以觀察到如下UX的一些“規(guī)律”:

  • 在不同的屏幕寬度下,應(yīng)用的整體風(fēng)格基本保持一致。
  • 在相近的屏幕寬度范圍內(nèi),應(yīng)用的布局基本不變;在不同的屏幕寬度范圍內(nèi),應(yīng)用的布局有較大差異。
  • 應(yīng)用在小屏幕下顯示的元素,是大屏幕中顯示元素的子集。
    • 考慮到屏幕尺寸及顯示效果,大屏幕中可以顯示的元素?cái)?shù)量一定不少于小屏幕。
    • 為充分利用屏幕尺寸優(yōu)勢(shì),大屏幕可以有其獨(dú)有的元素或設(shè)計(jì)(如本示例中的側(cè)邊欄)。

如此,既在各設(shè)備上體現(xiàn)了UX的一致性,也在各設(shè)備上體現(xiàn)了UX的差異性,從而既可以保障各設(shè)備上應(yīng)用界面的體驗(yàn),也可以最大程度復(fù)用界面代碼。

應(yīng)用UX設(shè)計(jì)章節(jié)中,將詳細(xì)介紹應(yīng)用的UX設(shè)計(jì)規(guī)則。

工程管理及調(diào)試

在本文IDE使用章節(jié)中,將詳細(xì)介紹一多的工程創(chuàng)建及管理等,本小節(jié)僅介紹最基礎(chǔ)的工程創(chuàng)建及多設(shè)備預(yù)覽調(diào)試。

工程創(chuàng)建

一多應(yīng)用的工程創(chuàng)建過(guò)程,與傳統(tǒng)應(yīng)用并無(wú)較大差異。只需在工程創(chuàng)建過(guò)程中,注意在“Device Type”選項(xiàng)中勾選所有該應(yīng)用期望運(yùn)行的目標(biāo)設(shè)備類型,保證后續(xù)該應(yīng)用可以在所有目標(biāo)設(shè)備上正確安裝即可。

預(yù)覽調(diào)試

在代碼開發(fā)過(guò)程中,可以開啟預(yù)覽器,并打開“Multi-profile preview”開關(guān),實(shí)時(shí)觀察應(yīng)用在不同設(shè)備下的表現(xiàn)。

特別的,還可以點(diǎn)擊“+ New Profile”按鈕,新增自定義預(yù)覽器。

頁(yè)面開發(fā)

天氣應(yīng)用中涉及較多的頁(yè)面和彈窗,本小節(jié)以天氣主頁(yè)為例,簡(jiǎn)單介紹不同設(shè)備下的頁(yè)面實(shí)現(xiàn)思路。

觀察天氣主頁(yè)在不同設(shè)備上的UX設(shè)計(jì)圖,可以進(jìn)行如下設(shè)計(jì):

  • 將天氣主頁(yè)劃分為9個(gè)基礎(chǔ)區(qū)域,如:
  • 基礎(chǔ)區(qū)域9僅在大設(shè)備上顯示,基礎(chǔ)區(qū)域1-8雖然在各設(shè)備上始終展示但其尺寸及區(qū)域內(nèi)的布局基本保持不變,可以結(jié)合自適應(yīng)布局能力以自定義組件的形式分別實(shí)現(xiàn)這9個(gè)基礎(chǔ)區(qū)域。
  • 基礎(chǔ)區(qū)域1-8之間的布局在不同設(shè)備上有較大差異,可以使用響應(yīng)式布局中的柵格布局能力實(shí)現(xiàn)組件間的布局效果。

  • 展開和隱藏側(cè)邊欄的功能可以通過(guò)側(cè)邊欄組件來(lái)實(shí)現(xiàn)。側(cè)邊欄是大設(shè)備上獨(dú)有的,借助響應(yīng)式布局中的媒體查詢能力,控制僅在大設(shè)備上展示側(cè)邊欄即可。

主頁(yè)基礎(chǔ)區(qū)域

天氣主頁(yè)中的9個(gè)基礎(chǔ)區(qū)域介紹及實(shí)現(xiàn)方案如下表所示。

編號(hào) 簡(jiǎn)介 實(shí)現(xiàn)方案
1 標(biāo)題欄 自適應(yīng)布局拉伸能力。
2 天氣概覽 Row和Column組件,并指定其子組件按照主軸起始方向?qū)R或居中對(duì)齊。
3 每小時(shí)天氣 自適應(yīng)布局延伸能力 。
4 每日天氣 自適應(yīng)布局延伸能力 。
5 空氣質(zhì)量 Canvas畫布組件繪制空氣質(zhì)量圖,并使用Row組件和Column組件控制內(nèi)部元素的布局。
6 生活指數(shù) 自適應(yīng)布局均分能力。
7 日出日落 Canvas畫布組件繪制日出日落圖 。
8 應(yīng)用信息 Row和Column組件,并指定其子組件居中對(duì)齊。
9 側(cè)邊導(dǎo)航欄 綜合運(yùn)用自適應(yīng)布局中的拉伸能力、占比能力和延伸能力 。

天氣主頁(yè)涉及的內(nèi)容較多,因篇幅限制,本小節(jié)僅介紹區(qū)域3(每小時(shí)天氣)的實(shí)現(xiàn),讀者可以自行查看開源代碼,了解其它基礎(chǔ)區(qū)域的實(shí)現(xiàn)。

延伸能力是指容器組件內(nèi)的子組件,按照其在列表中的先后順序,隨容器組件尺寸變化顯示或隱藏。隨著可用顯示區(qū)域的增加,用戶可以看到的“每小時(shí)天氣”信息也不斷增加,故“每小時(shí)天氣”可以通過(guò)延伸能力實(shí)現(xiàn),其核心代碼如下所示。

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() {
    // 通過(guò)列表組件實(shí)現(xiàn)延伸能力
    List() {
      LazyForEach(this.hoursDataResource, (hoursItem:IDataSource) => {
        ListItem() {
          // 具體每個(gè)小時(shí)的天氣情況
          Column() { 
              // ... 
            }
        }
      })
    }
    .height(Style.CARD_HEIGHT)
    .borderRadius(Style.NORMAL_RADIUS)
    .backgroundColor(Style.CARD_BACKGROUND_COLOR)
    // 將列表方向設(shè)置為水平方向
    .listDirection(Axis.Horizontal)
  }
}

城市天氣詳情

天氣主頁(yè)右側(cè)的城市天氣詳情由區(qū)域1-8組成,區(qū)域1(標(biāo)題欄)始終固定在頁(yè)面頂部,區(qū)域2-8在不同設(shè)備下的布局不同且可以隨頁(yè)面上下滾動(dòng)。本小節(jié)介紹如何實(shí)現(xiàn)城市天氣詳情中區(qū)域2~8的布局效果。

設(shè)備屏幕可能無(wú)法一次性顯示區(qū)域2-8的所有內(nèi)容,故需要在外層增加滾動(dòng)組件(即Scroll組件)以支持上下滾動(dòng)。不同設(shè)備下區(qū)域2-8的相對(duì)位置一共有三套不同的布局,可以借助響應(yīng)式布局中的柵格布局實(shí)現(xiàn)這一效果。本示例中將柵格在不同場(chǎng)景下分別劃分為4列、8列和12列,區(qū)域2-8在不同場(chǎng)景下的布局如下表所示。

說(shuō)明: 為提升用戶體驗(yàn),大設(shè)備側(cè)邊欄隱藏狀態(tài)下,每日天氣與空氣質(zhì)量的相對(duì)順序發(fā)生了改變??梢哉{(diào)整通過(guò)GridCol柵格子組件的order屬性,實(shí)現(xiàn)目標(biāo)效果。

import AirQuality from './AirQuality'; //組件請(qǐng)參考相關(guān)實(shí)例
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() {
    // 支持滾動(dòng)
    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)
        }
        // 每小時(shí)天氣
        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 })
        }
        // 空氣質(zhì)量
        GridCol({ span: 4, order: {sm: 4, md: 4, lg: this.showSideBar ? 4 : 3} }) {
          AirQuality({ airData: this.cityListData.airData, airIndexData: this.cityListData.airIndex })
        }
        // 生活指數(shù)
        GridCol({ span: 4, order: 5 }) {
          LifeIndex({ lifeData: this.cityListData.suitDate })
        }
        // 日出日落
        GridCol({ span: 4, order: 6 }) {
          SunCanvas()
        }
        // 應(yīng)用信息
        GridCol({ span: { sm: 4, md: 8, lg: this.showSideBar ? 8 : 12 }, order: 7 }) {
          IndexEnd()
        }
      }
    }
    .width('100%')
  }
}

主頁(yè)整體實(shí)現(xiàn)

綜合考慮各設(shè)備下的效果,天氣主頁(yè)的根節(jié)點(diǎn)使用側(cè)邊欄組件:

  • 小設(shè)備和中設(shè)備既不展示側(cè)邊欄,也不提供控制側(cè)邊欄顯示和隱藏的按鈕。
  • 大設(shè)備默認(rèn)展示側(cè)邊欄,同時(shí)提供控制側(cè)邊欄顯示和隱藏的按鈕。

另外主頁(yè)右側(cè)的城市天氣詳情,支持左右滑動(dòng)切換城市,可以使用Swiper組件實(shí)現(xiàn)目標(biāo)效果。

  • 小設(shè)備和中設(shè)備開啟Swiper組件的導(dǎo)航點(diǎn),引導(dǎo)用戶通過(guò)左右滑動(dòng)切換不同城市。
  • 大設(shè)備中用戶通過(guò)點(diǎn)擊側(cè)邊欄中的城市列表即可高效的切換不同城市,此時(shí)需要關(guān)閉Swiper組件的導(dǎo)航點(diǎn)。
import HomeContent from './home/HomeContent'; //組件請(qǐng)參考相關(guān)實(shí)例
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) {
      // 左側(cè)側(cè)邊欄
      SideContent({ showSideBar: $showSideBar })
      // 右側(cè)內(nèi)容區(qū)
      Flex({direction: FlexDirection.Column}) {
        // 基礎(chǔ)區(qū)域1標(biāo)題欄
        IndexTitleBar({ curBp: this.curBp, showSideBar: $showSideBar })
          .height(56)
        // 天氣詳情,通過(guò)Swiper組件實(shí)現(xiàn)左右滑動(dòng)切換城市的效果
        Swiper() {
          ForEach(this.cityListWeatherData, (item:CityListData, index) => {
            HomeContent({ showSideBar: this.showSideBar, cityListData: item, index: index })
          })
        }
        // 大設(shè)備關(guān)閉導(dǎo)航點(diǎn)
        .indicator(this.curBp !== 'lg')
        .width('100%')
      }
    }
    .height('100%')
    .sideBarWidth('33.3%')
    // 通過(guò)狀態(tài)變量,控制不同設(shè)備下側(cè)邊欄的顯隱狀態(tài)
    .showSideBar(this.showSideBar)
  }
}

最終,天氣首頁(yè)的運(yùn)行效果如下圖所示。

功能開發(fā)

應(yīng)用開發(fā)不僅包含應(yīng)用頁(yè)面開發(fā),還包括應(yīng)用后端功能開發(fā)以及服務(wù)器端開發(fā)等。服務(wù)器端開發(fā)不在本文的討論范圍內(nèi),本小節(jié)僅介紹多設(shè)備上應(yīng)用功能開發(fā)的注意事項(xiàng)。

如前文所示,本示例的目標(biāo)運(yùn)行設(shè)備是小設(shè)備、中設(shè)備和大設(shè)備,對(duì)應(yīng)實(shí)際的設(shè)備類型為默認(rèn)設(shè)備和平板等。這些設(shè)備運(yùn)行的都是標(biāo)準(zhǔn)系統(tǒng),其系統(tǒng)能力一致,所以無(wú)需做特別考慮。但是在超小設(shè)備(對(duì)應(yīng)的實(shí)際設(shè)備類型為智能穿戴設(shè)備等)上,考慮CPU、內(nèi)存、硬盤等硬件限制,往往會(huì)對(duì)系統(tǒng)進(jìn)行裁剪。如果在應(yīng)用后端功能開發(fā)時(shí)調(diào)用當(dāng)前系統(tǒng)沒(méi)有的能力,就可能會(huì)引發(fā)異常。

通常有兩種方式解決上述問(wèn)題:

  • 在應(yīng)用安裝包中描述其需要的系統(tǒng)能力,保證本應(yīng)用僅被分發(fā)和安裝到可以滿足其訴求的系統(tǒng)中。
  • 在使用特定系統(tǒng)能力前,通過(guò)canIUse接口判斷系統(tǒng)能力是否存在,進(jìn)而執(zhí)行不同的邏輯。

相關(guān)實(shí)例

針對(duì)天氣應(yīng)用,有以下相關(guān)實(shí)例可供參考:

天氣應(yīng)用:天氣應(yīng)用示例

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容