HarmonyOS Next 入門實戰 - 基礎元件、頁面實現

睡精灵s發表於2024-12-09

基礎元件

常用元件

  • Text:顯示文字內容
  • Image:顯示圖片
  • Button:顯示一個按鈕
  • Column: 縱向佈局
  • Row:橫向佈局
  • List:列表

各元件的用法

Text("文字元件")
  .fontColor(Theme.Color.textPrimary)
  .fontWeight(FontWeight.Medium)
  .fontSize(20)

Image($r('app.media.banner'))
        .objectFit(ImageFit.Fill)
        .width('100%')
        .height(100)
        .opacity(191)
 
Button("按鈕").onClick((event) => {
    //按鈕點選事件
})

Column() {
  Text("文字1")
  .fontColor(Theme.Color.textPrimary)
  .fontWeight(FontWeight.Medium)
  .fontSize(20)
  Text("文字2")
  .fontColor(Theme.Color.textPrimary)
  .fontWeight(FontWeight.Medium)
  .fontSize(20)
  Text("文字3")
  .fontColor(Theme.Color.textPrimary)
  .fontWeight(FontWeight.Medium)
  .fontSize(20)
}.width('100%')

Row() {
  Text("文字1")
  .fontColor(Theme.Color.textPrimary)
  .fontWeight(FontWeight.Medium)
  .fontSize(20)
  Text("文字2")
  .fontColor(Theme.Color.textPrimary)
  .fontWeight(FontWeight.Medium)
  .fontSize(20)
  Text("文字3")
  .fontColor(Theme.Color.textPrimary)
  .fontWeight(FontWeight.Medium)
  .fontSize(20)
}.width('100%')

List() {
  ForEach(this.items, (item: string, index) => {
    ListItem(){
      Text(item).padding(10).width('100%')
    }
  })
}

Grid
用於展示網格列表

Grid(){
  GridItem(){
    Text("item1").padding(10)
  }
  GridItem(){
    Text("item2").padding(10)
  }
  GridItem(){
    Text("item12").padding(10)
  }
}.columnsTemplate("1f 1f 1f")  //列分佈規則
.rowsTemplate("1f 1f 1f")		//行分佈規則

Grid的誇行/列設定

在建立Grid時可以傳入GridLayoutOptions來實現網格所佔行/列數

  1. regularSize,型別為[number,number],用來定義一般規則的Item所佔的行/列數
  2. irregularIndexes,型別為number[],用來指定不遵循一般規則的Item索引。
  3. onGetIrregularSizeByIndex,型別(index: number) => [number, number]的回撥函式,irregularIndexes中配置的索引會回撥到此函式,返回結果表示該Item所佔用的行/列數,[所佔行數,所佔列數]
@Preview
@Component
struct GridList {
  item: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  layoutOption: GridLayoutOptions = {
    regularSize: [1, 1],  //一般規則的item佔用1行1列
    irregularIndexes: [0, 5], //非一般規則item的索引
    onGetIrregularSizeByIndex: (index: number) => {
      if (index == 0) {
        return [1, 2]  //0索引處的item佔用1行2列
      } else {
        return [2, 1]  //5索引處的item佔用2行1列
      }
    }
  }

  build() {
    Grid(null, this.layoutOption) {
      ForEach(this.item, (item: number, index) => {
        GridItem() {
          Stack() {
            Text(item + "").textAlign(TextAlign.Center)
          }.width('100%').height(index == 5 ? 105 : 50)
        }.backgroundColor(Color.Pink)
      }, (item: number, index) => {
        return item + ""
      })
    }
    .columnsTemplate('1fr 1fr')
    .rowsGap(5)
    .columnsGap(5)
    .width('100%')
    .height('100%')
  }
}

WaterFlow
瀑布流

WaterFlow() {
  FlowItem() {
     Text("item1").padding(10)
  }
  FlowItem() {
   Text("item2").padding(10)
  }
}

瀑布流誇行/列設定

使用瀑布流的分組資訊(WaterFlowSections)可以設定item不同列數的混合佈局。
WaterFlowSections 中可以設定多個分組資訊,每個分組資訊包含如下內容:
● itemsCount 分組中的item數量
● crossCount 瀑布流的行/列數
● columnsGap/rowsGap:列/行間距
● margin 分組的外邊距

@Preview
@Component
struct WaterFlowListView {
  items: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  @State sections: WaterFlowSections = new WaterFlowSections()

  aboutToAppear(): void {
    this.sections.push({
      itemsCount: 1, //分組中的item數量
      crossCount: 1, //分組中的列數
      columnsGap: 8, //列間距
      rowsGap: 8,     //行間距
    })
    this.sections.push({
      itemsCount: 8,
      crossCount: 2,
      columnsGap: 8,
      rowsGap: 8,
      margin:{
        top: 8
      }
    })
    this.sections.push({
      itemsCount: 1,
      crossCount: 1,
      columnsGap: 8,
      rowsGap: 8,
      margin:{
        top: 8
      }
    })
  }

  build() {
    WaterFlow({ sections: this.sections }) {
      ForEach(this.items, (item: number, index) => {
        FlowItem() {
          Stack() {
            Text(item + "").textAlign(TextAlign.Center)
          }.height(index*10 + 30)
        }.backgroundColor(Color.Pink).width('100%')
      }, (item: number, index) => {
        return item + ""
      })
    }
    .width('100%')
    .height('100%')
  }
}

頁面實現

準備資料
首先需要準備資料,包括詩詞內容,小知識,作者。
json檔案內容如下:

[
  {
    "title": "滿江紅·寫懷",
    "dynasty": "南宋",
    "author": "岳飛",
    "introduction": "此詞上片寫作者悲憤中原重陷敵手,痛惜前功盡棄的局面,也表達自己繼續努力,爭取壯年立功的心願。\n此詞下片運轉筆端,抒寫詞人對於民族敵人的深仇大恨,統一祖國的殷切願望,忠於朝廷即忠於祖國的赤誠之心。",
    "text": "怒髮衝冠,憑闌處、瀟瀟雨歇。\n抬望眼,仰天長嘯,壯懷激烈。\n三十功名塵與土,八千里路雲和月。\n莫等閒、白了少年頭,空悲切。\n靖康恥,猶未雪。\n臣子恨,何時滅。\n駕長車,踏破賀蘭山缺。\n壯志飢餐胡虜肉,笑談渴飲匈奴血。\n待從頭、收拾舊山河,朝天闕。",
    "textAlign": "center",
    "translation": "氣得頭髮豎起,以至於將帽子頂起,登高倚欄杆,一場瀟瀟細雨剛剛停歇。\n\n抬頭望眼四望遼闊一片,仰天長聲嘯嘆,一片報國之心充滿心懷。\n\n三十多年來雖已建立一些功名,但如同塵土微不足道,南北轉戰八千里,經過多少風雲人生。\n\n不要虛度年華,花白了少年黑髮,只有獨自悔恨悲悲切切。\n\n靖康年的奇恥,尚未洗雪。\n\n臣子憤恨,何時才能泯滅。\n\n我要駕著戰車向賀蘭山進攻,連賀蘭山也要踏為平地。\n\n我滿懷壯志,打仗餓了就吃敵人的肉,談笑渴了就喝敵人的鮮血。\n\n我要從頭再來,收復舊日河山,朝拜故都京闕。",
    "searchKey": "manjianghong|mjh|mjhxh|song|yuefei|yf"
  },
  {
    "title": "水調歌頭·明月幾時有",
    "dynasty": "北宋",
    "author": "蘇軾",
    "introduction": "詞前的小序交待了寫詞的過程:“丙辰中秋,歡飲達旦,大醉。作此篇,兼懷子由。”蘇軾因為與當權的變法者王安石等人政見不同,自求外放,輾轉在各地為官。他曾經要求調任到離蘇轍較近的地方為官,以求兄弟多多聚會。公元1074年(熙寧七年)蘇軾差知密州。到密州後,這一願望仍無法實現。公元1076年的中秋,皓月當空,銀輝遍地,詞人與胞弟蘇轍分別之後,已七年未得團聚。此刻,詞人面對一輪明月,心潮起伏,於是乘酒興正酣,揮筆寫下了這首名篇。",
    "text": "丙辰中秋,歡飲達旦,大醉,作此篇,兼懷子由。\n\n明月幾時有?把酒問青天。不知天上宮闕,今夕是何年。我欲乘風歸去,又恐瓊樓玉宇,高處不勝寒。起舞弄清影,何似在人間。\n\n轉朱閣,低綺戶,照無眠。不應有恨,何事長向別時圓?人有悲歡離合,月有陰晴圓缺,此事古難全。但願人長久,千里共嬋娟。",
    "textAlign": "left",
    "translation": "丙辰年的中秋節,高興地喝酒直到第二天早晨,喝到大醉,寫了這首詞,同時思念弟弟蘇轍。\n\n明月從什麼時候才開始出現的?我端起酒杯遙問蒼天。不知道在天上的宮殿,何年何月。我想要乘御清風回到天上,又恐怕在美玉砌成的樓宇,受不住高聳九天的寒冷。翩翩起舞玩賞著月下清影,哪像是在人間。\n\n月兒轉過硃紅色的樓閣,低低地掛在雕花的窗戶上,照著沒有睡意的自己。明月不該對人們有什麼怨恨吧,為什麼偏在人們離別時才圓呢?人有悲歡離合的變遷,月有陰晴圓缺的轉換,這種事自古來難以周全。只希望這世上所有人的親人能平安健康,即便相隔千里,也能共享這美好的月光。",
    "rectify": {
      "高觸不勝寒": "高觸不勝寒"
    },
    "searchKey": "shuidiaogetou|mingyuejishiyou|sdgt|myjsy|song|sushi|ss"
  },
  {
    "title": "登高",
    "dynasty": "唐",
    "author": "杜甫",
    "introduction": "此詩作於公元767年(唐代宗大曆二年)秋天,杜甫時在夔州。這是他在五十六歲時寫下的。一天他獨自登上夔州白帝城外的高臺,登高臨眺,蕭瑟的秋江景色,引發了他身世飄零的感慨,滲入了他老病孤愁的悲哀。於是,就有了這首被譽為“七律之冠”的《登高》。",
    "text": "風急天高猿蕭哀,渚清沙白鳥飛回;\n無邊落木蕭蕭下,不盡長江滾滾來。\n萬里悲秋常作客,百年多病獨登臺;\n艱難苦恨繁霜鬢,潦倒新停濁酒杯。",
    "textAlign": "center",
    "translation": "風急天高猿猴啼叫顯得十分悲哀,水清沙白的河洲上有鳥兒在盤旋。\n無邊無際的樹木蕭蕭地飄下落葉,長江滾滾湧來奔騰不息。\n悲對秋景感慨萬里漂泊常年為客,一生當中疾病纏身今日獨上高臺。\n歷盡了艱難苦恨白髮長滿了雙鬢,衰頹滿心偏又暫停了澆愁的酒杯。",
    "searchKey": "denggao|dg|tang|dufu|dg"
  },
  ……
]
[
  {
    "title": "唐宋八大家",
    "text": "唐宋八大家,又稱為“唐宋散文八大家”,是唐代和宋代八位散文家的合稱,分別為唐代韓愈、柳宗元和宋代歐陽修、蘇洵、蘇軾、蘇轍、王安石、曾鞏八位。\n其中韓愈、柳宗元是唐代古文運動的領袖,歐陽修、三蘇(蘇軾、蘇轍、蘇洵)等四人是宋代古文運動的核心人物,王安石、曾鞏是臨川文學的代表人物。他們先後掀起的古文革新浪潮,使詩文發展的陳舊面貌煥然一新。\n八大家中蘇家父子兄弟有三人,人稱“三蘇”,分別為蘇洵、蘇軾、蘇轍,又有“一門三學士”之譽。故可用“韓柳歐王曾三蘇”概括。"
  },
  {
    "title": "醉八仙",
    "text": "醉八仙,指唐朝嗜酒的八位學者名人,亦稱酒中八仙、飲中八仙。《新唐書·李白傳》載,李白、賀知章、李適之、汝陽王李璡、崔宗之、蘇晉、張旭、焦遂為“酒中八仙人”。杜甫有《飲中八仙歌》。瓷器畫面繪飲中八仙,每每於人物之上書以人名,以清朝為多見。\n一仙賀知章:知章騎馬似乘船,眼花落井水底眠。\n二仙汝陽王:汝陽三鬥始朝天,道逢麴車口流涎,恨不移封向酒泉。\n三仙李適之:左相日興費萬錢,飲如長鯨吸百川,銜杯樂聖稱避賢。\n四仙崔宗之:宗之瀟灑美少年,舉觴白眼望青天,皎如玉樹臨風前。\n五仙蘇晉:蘇晉長齋繡佛前,醉中往往愛逃禪。\n六仙李白:李白一斗詩百篇,長安市上酒家眠。天子呼來不上船,自稱臣是酒中仙。\n七仙張旭:張旭三杯草聖傳,脫帽露頂王公前,揮毫落紙如雲煙。\n八仙焦遂:焦遂五斗方卓然,高談雄辯驚四筵。"
  },
  ……
]
[
  {
    "name": "蘇軾",
    "dynasty": "北宋",
    "introduction": "蘇軾(1037年—1101年),字子瞻,又字和仲,號鐵冠道人、東坡居士,世稱蘇東坡、蘇仙、坡仙。眉州眉山(今四川省眉山市)人,北宋文學家,書法家、畫家,歷史治水名人。與父蘇洵、弟蘇轍三人並稱“三蘇”。\n嘉祐二年(1057年),參加殿試中乙科,賜進士及第(一說賜進士出身)。嘉祐六年(1061年),參加制科考試,授大理評事、籤書鳳翔府判官。宋神宗時,曾在杭州、密州、徐州、湖州等地任職。元豐三年(1080年),因“烏臺詩案”,被貶為黃州團練副使。宋哲宗即位後,出任兵部尚書、禮部尚書等職,外放治理杭州、潁州、揚州、定州等地。隨著新黨執政,又被貶惠州、儋州。宋徽宗時,獲赦北還,病逝於常州。南宋時期,追贈太師,諡號“文忠”。\n蘇軾是北宋中期文壇領袖,在詩、詞、文、書、畫等方面取得很高成就。其詩題材廣闊,清新豪健,善用誇張比喻,獨具風格,與黃庭堅並稱“蘇黃”;其詞開豪放一派,與辛棄疾同是豪放派代表,並稱“蘇辛”;其文著述宏富,縱橫恣肆,豪放自如,與歐陽修並稱“歐蘇”,與韓愈、柳宗元、歐陽修、蘇洵、蘇轍、王安石、曾鞏合稱“唐宋八大家”;善書法,與黃庭堅、米芾、蔡襄合稱“宋四家”;擅長文人畫,尤擅墨竹、怪石、枯木等。作品有《東坡七集》《東坡易傳》《東坡樂府》《寒食帖》《瀟湘竹石圖》《枯木怪石圖》等。"
  },
  {
    "name": "杜甫",
    "dynasty": "唐",
    "introduction": "杜甫(712年2月12日—770年),字子美,自號少陵野老,祖籍襄陽(今屬湖北),自其曾祖時遷居鞏縣(今河南鞏義西南)。唐代著名現實主義詩人。與李白合稱“李杜”“大李杜”,也常被稱為“老杜”。\n杜甫自幼好學,知識淵博,頗有政治抱負。唐玄宗開元后期,舉進士不第,漫遊各地。後寓居長安近十年,未能有所施展,生活貧困,逐漸對當時的社會狀況有較深的認識。後靠獻賦才得授小官。安史之亂爆發、長安失陷後,他曾被困城中半年,後逃至鳳翔,被唐肅宗拜為左拾遺,世稱“杜拾遺”。長安收復後,隨肅宗還京,又被外放為華州司功參軍。期間創作了《登高》《春望》《北征》以及“三吏”“三別”等名作。後棄官移家至成都,一度在劍南節度使嚴武幕中任參謀,被表授為檢校工部員外郎,故世稱“杜工部”。晚年攜家出蜀,於大曆五年(770年)冬在輾轉途中逝世,享年五十九歲。\n杜甫善於運用各種詩歌形式,尤長於律詩,風格多樣,而以沉鬱為主;語言精練,具有高度的表達能力。繼承和發展《詩經》以來注重反映社會現實的優良文學傳統,成為中國古代詩歌藝術發展的又一高峰,被後人公認為詩歌史上的“集大成者”。他的人格,也被認為是中華民族文人品格的楷模。自晚唐兩宋後,杜甫逐漸聲名遠播,對中國文學和日本文學都產生了深遠的影響。後世尊稱他為“詩聖”,稱其詩為“詩史” 。其傳世作品大多集於《杜工部集》。"
  },
  {
    "name": "岳飛",
    "dynasty": "南宋",
    "introduction": "岳飛(1103年3月24日—1142年1月27日),字鵬舉,宋朝相州湯陰(今河南湯陰)人,祖籍東昌(今山東聊城),南宋時期抗金名將、軍事家、戰略家、民族英雄、書法家、詩人,位列南宋“中興四將”之首。\n岳飛從二十歲起,曾先後四次從軍。自建炎二年(1128年)遇宗澤至紹興十一年(1141年)止,先後參與、指揮大小戰鬥數百次。金軍攻打江南時,獨樹一幟,力主抗金,收復建康。紹興四年(1134年),收復襄陽六郡。紹興六年(1136年),率師北伐,順利攻取商州、虢州等地。紹興十年(1140年),完顏宗弼毀盟攻宋,岳飛揮師北伐,兩河人民奔走相告,各地義軍紛紛響應,夾擊金軍。岳家軍先後收復鄭州、洛陽等地,在郾城、潁昌大敗金軍,進軍朱仙鎮。宋高宗趙構和宰相秦檜卻一意求和,以十二道“金字牌”催令班師。在宋金議和過程中,岳飛遭受秦檜、張俊等人誣陷入獄。1142年1月,以莫須有的罪名,與長子岳雲、部將張憲一同遇害。宋孝宗時,平反昭雪,改葬於西湖畔棲霞嶺,追諡武穆,後又追諡忠武,封鄂王。 \n岳飛是南宋傑出的統帥,他重視人民抗金力量,締造了“連結河朔”之謀,主張黃河以北的民間抗金義軍和宋軍互相配合,以收復失地;治軍賞罰分明,紀律嚴整,又能體恤部屬,以身作則,率領的“岳家軍”號稱“凍死不拆屋,餓死不擄掠”。金軍有“撼山易,撼岳家軍難”的評語,以示對岳家軍的由衷敬佩。\n岳飛的文才同樣卓越,其代表詞作《滿江紅·怒髮衝冠》是千古傳誦的愛國名篇,後人輯有文集傳世。"
  },
  ……
}

將json檔案放置在資源目錄 resources/rawfile/ 下載
使用時透過 import 匯入

import poetryList from 'resources/rawfile/poetry.json';
import tips from 'resources/rawfile/tips.json';

根據json資料格式定義資料實體類

export interface Poetry {
  uuid?: string
  title: string
  dynasty: string
  author: string
  introduction: string
  text: string
  textAlign: string
  translation: string
  rectify?: object
  searchKey: string
}

export class Tip {
  uuid: string
  title: string
  text: string
}

首頁實現

首頁實現效果

首頁內容包括一個隨機的小知識和詩詞列表,因此我們可以使用List,Grid,WaterFlow元件來實現列表展示,這裡我們以WaterFlow為例。

小知識佈局

@Component
export struct TipView {
  @Prop tip: Tip
  viewHeight: number = 152
  textFontSize: number = 19
  textController: TextController = new TextController();

  applyTextStyle() {
    let style = TextStyleUtils.buildParagraphIndentStyle(this.tip.text, this.textFontSize * 2)
    this.textController.setStyledString(style)
  }

  build() {
    Stack({
      alignContent: Alignment.TopStart
    }) {
      Image($r('app.media.tip_banner'))
        .objectFit(ImageFit.Fill)
        .width('100%')
        .height(this.viewHeight)
        .opacity(191)
      Column() {
        Text(this.tip.title)
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
        Text(undefined, { controller: this.textController })
          .fontSize(this.textFontSize)
          .lineHeight(26)
          .maxLines(4)
          .textOverflow({
            overflow: TextOverflow.Ellipsis
          })
          .margin({ top: 5 })
          .width("100%")
          .onAppear(() => {
            this.applyTextStyle()
          })
      }
      .alignItems(HorizontalAlign.Start)
      .padding({
        top: 10,
        bottom: 10,
        left: 12,
        right: 12
      })
      .width('100%')
      .constraintSize({ maxHeight: this.viewHeight })
      .backgroundColor(Theme.Color.tipShade)
    }
    .clip(true)
    .borderRadius(10)
  }
}

小知識內容涉及多個段落,為了閱讀方便,段落開始需要縮排2個漢字,HarmonyOS提供了 ParagraphStyle 類設定段落樣式。

export class TextStyleUtils {

  static buildParagraphIndentStyle(text: string, offset: number): MutableStyledString {
    let paragraphStyle: ParagraphStyle = new ParagraphStyle({ textIndent: LengthMetrics.vp(offset) });
    let regArray = text.matchAll(/\n/g)
    let styleArray: Array<StyleOptions> = []
    styleArray.push({
      start: 0,
      length: 3,
      styledKey: StyledStringKey.PARAGRAPH_STYLE,
      styledValue: paragraphStyle
    })
    for (let match of regArray) {
      styleArray.push({
        start: match.index,
        length: 3,
        styledKey: StyledStringKey.PARAGRAPH_STYLE,
        styledValue: paragraphStyle
      })
    }
    return new MutableStyledString(text, styleArray)
  }
}

詩詞列表項佈局


@Component
struct DataItemView {
  @Prop item: Poetry

  build() {

    Column() {
      Text(this.item.title)
        .fontColor(Theme.Color.textPrimary)
        .fontWeight(FontWeight.Medium)
        .fontSize(20)
      Text(`[${this.item.dynasty}]${this.item.author}`)
        .fontColor(Theme.Color.textSecondary)
        .fontSize(16)
        .margin({ top: 2 })
      Text(this.item.introduction)
        .fontColor(Theme.Color.textSecondary)
        .fontSize(16)
        .margin({ top: 5 })
        .maxLines(3)
        .textOverflow({
          overflow: TextOverflow.Ellipsis
        })
    }
    .alignItems(HorizontalAlign.Start)
    .padding(10)
  }
}

瀑布流列表

列表第一項是小知識佈局,需要誇2列顯示,因此列表項分組如下:

  1. 小知識佈局,數量為1,展示1列
  2. 詩詞項佈局,數量為詩詞列表數量,展示2列
  3. 底部留白,數量為1,展示1列
@Component
struct WaterFlowView {
  @Prop tip: Tip
  @Prop poetryList: LazyDataSource<Poetry>
  @State sections: WaterFlowSections = new WaterFlowSections()

  aboutToAppear(): void {
    this.sections.push({
      itemsCount: 1,
      crossCount: 1,
    })
    this.sections.push({
      itemsCount: this.poetryList.totalCount(),
      crossCount: 2,
      columnsGap: 8,
      rowsGap: 8,
    })
    this.sections.push({
      itemsCount: 1,
      crossCount: 1,
    })
  }

  build() {
    WaterFlow({ sections: this.sections, layoutMode: WaterFlowLayoutMode.SLIDING_WINDOW }) {
      FlowItem() {
        TipView({
          tip: this.tip,
        })
      }
      .padding({ top: 8, bottom: 8 })
      .onClick(() => {
      })

      LazyForEach(this.poetryList,
        (item: Poetry, index) => {
          FlowItem() {
            DataItemView({
              item: item
            })
          }
          .width('100%')
          .borderRadius(10)
          .backgroundColor(Theme.Color.inputEditTextBackground)
          .onClick(() => {
          })
        },
        (item: Poetry, index) => {
          return item.uuid
        }
      )
      FlowItem() {
        Text().height(8).width('100%')
      }
    }
    .width('100%')
    .height('100%')
    .padding({ left: 8, right: 8 })
  }

詳情頁實現
詳情頁實現效果

詩詞正文部分可以根據內容居中或居左展示,【介紹】部分也是需要段落縮排(參考小知識佈局)。詳情頁佈局比較簡單,不在詳述。


本文的技術設計和實現都是基於作者工作中的經驗總結,如有錯誤,請留言指正,謝謝。

相關文章